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