Initial commit
commit
10f5a19840
@ -0,0 +1,40 @@
|
|||||||
|
# Abortable promises ⚡
|
||||||
|
|
||||||
|
A module for node that lets you run complex sequence of async functions and:
|
||||||
|
|
||||||
|
1. Use an external signal to stop the sequence of steps
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const sleep = promisify((timeout: number, callback: (...args: any[]) => void) =>
|
||||||
|
setTimeout(callback, timeout)
|
||||||
|
);
|
||||||
|
|
||||||
|
const a = new AbortablePromise(async function* () {
|
||||||
|
yield await sleep(1000);
|
||||||
|
console.log("awaited 100");
|
||||||
|
yield await sleep(2000);
|
||||||
|
console.log("awaited 200");
|
||||||
|
yield await sleep(3000);
|
||||||
|
console.log("awaited 300");
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => a.abort(), 1500); // doesn't get to sleep(3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Use an external signal to kill an already started asynchronous step
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const b = new AbortablePromise(async function* (await_or_abort) {
|
||||||
|
const ping = await deferedSpawn("ping", ["8.8.8.8"]);
|
||||||
|
while (true) {
|
||||||
|
console.log(
|
||||||
|
await await_or_abort(ping.waitForNextData(), () => {
|
||||||
|
ping.kill();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => b.abort(), 5000); // doesn't get to 5th ping
|
||||||
|
```
|
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "abortable-promise",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Abortable promises based on async generators",
|
||||||
|
"main": "./lib/index.js",
|
||||||
|
"types": "./@types/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"watch": "npm run build -- --watch",
|
||||||
|
"install": "npm run build",
|
||||||
|
"prepare": "npm run build"
|
||||||
|
},
|
||||||
|
"repository": "gitea@git.kuba-orlik.name:kuba/async-spawner.git",
|
||||||
|
"author": "Kuba Orlik",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^14.14.19",
|
||||||
|
"emittery": "^0.8.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^4.1.3"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
import { deferedSpawn } from "async-spawner";
|
||||||
|
import Emittery from "emittery";
|
||||||
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
//needed to pass information on wether or not the promise has been aborted witout 'this'
|
||||||
|
class AbortableEmittery extends Emittery {
|
||||||
|
public aborted = false;
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
this.aborted = true;
|
||||||
|
this.emit("abort");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AbortablePromise<T> extends Promise<T | null> {
|
||||||
|
private emitter: AbortableEmittery;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
make_generator: (
|
||||||
|
await_or_abort: (
|
||||||
|
promise: Promise<any>,
|
||||||
|
on_abort: () => void
|
||||||
|
) => Promise<void>
|
||||||
|
) => AsyncGenerator
|
||||||
|
) {
|
||||||
|
const emitter = new AbortableEmittery();
|
||||||
|
const await_or_abort = (promise: Promise<any>, on_abort: () => void) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let resolved = false;
|
||||||
|
emitter.on("abort", () => {
|
||||||
|
if (!resolved) {
|
||||||
|
on_abort();
|
||||||
|
resolve(null);
|
||||||
|
resolved = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promise
|
||||||
|
.then((result) => {
|
||||||
|
if (!resolved) {
|
||||||
|
resolve(result);
|
||||||
|
resolved = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
super(async (resolve, reject) => {
|
||||||
|
let step_value;
|
||||||
|
const generator = make_generator(await_or_abort);
|
||||||
|
do {
|
||||||
|
if (emitter.aborted) {
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
step_value = await generator.next();
|
||||||
|
} while (!step_value.done);
|
||||||
|
});
|
||||||
|
this.emitter = emitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
this.emitter.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const sleep = promisify((timeout: number, callback: (...args: any[]) => void) =>
|
||||||
|
// setTimeout(callback, timeout)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const a = new AbortablePromise(async function* () {
|
||||||
|
// yield await sleep(1000);
|
||||||
|
// console.log("awaited 100");
|
||||||
|
// yield await sleep(2000);
|
||||||
|
// console.log("awaited 200");
|
||||||
|
// yield await sleep(3000);
|
||||||
|
// console.log("awaited 300");
|
||||||
|
// });
|
||||||
|
|
||||||
|
// setTimeout(() => a.abort(), 1500);
|
||||||
|
|
||||||
|
const b = new AbortablePromise(async function* (await_or_abort) {
|
||||||
|
const ping = await deferedSpawn("ping", ["8.8.8.8"]);
|
||||||
|
while (true) {
|
||||||
|
console.log(
|
||||||
|
await await_or_abort(ping.waitForNextData(), () => {
|
||||||
|
ping.kill();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => b.abort(), 5000);
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"target": "ES6",
|
||||||
|
"declaration": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["es6", "esnext"],
|
||||||
|
"outDir": "lib",
|
||||||
|
"checkJs": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"declarationDir": "@types",
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
Loading…
Reference in New Issue