├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github └── workflows │ ├── dev.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── package.json └── test ├── arguments.js ├── callbacks.js ├── child_processes.js ├── observables.js ├── promises.js ├── streams.js ├── streamx.js └── types ├── callback.ts ├── child_processes.ts ├── observables.ts ├── promises.ts ├── streams.ts ├── tsconfig.json └── various.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | end_of_line = lf 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "gulp" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: dev 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | env: 9 | CI: true 10 | 11 | jobs: 12 | prettier: 13 | name: Format code 14 | runs-on: ubuntu-latest 15 | if: ${{ github.event_name == 'push' }} 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Prettier 22 | uses: gulpjs/prettier_action@v3.0 23 | with: 24 | commit_message: 'chore: Run prettier' 25 | prettier_options: '--write .' 26 | 27 | test: 28 | name: Tests for Node ${{ matrix.node }} on ${{ matrix.os }} 29 | runs-on: ${{ matrix.os }} 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | node: [10, 12, 14, 16] 35 | os: [ubuntu-latest, windows-latest, macos-latest] 36 | 37 | steps: 38 | - name: Clone repository 39 | uses: actions/checkout@v2 40 | 41 | - name: Set Node.js version 42 | uses: actions/setup-node@v2 43 | with: 44 | node-version: ${{ matrix.node }} 45 | 46 | - run: node --version 47 | - run: npm --version 48 | 49 | - name: Install npm dependencies 50 | run: npm install 51 | 52 | - name: Run lint 53 | run: npm run lint 54 | 55 | - name: Run tests 56 | run: npm test 57 | 58 | - name: Coveralls 59 | uses: coverallsapp/github-action@v1.1.2 60 | with: 61 | github-token: ${{ secrets.GITHUB_TOKEN }} 62 | flag-name: ${{matrix.os}}-node-${{ matrix.node }} 63 | parallel: true 64 | 65 | coveralls: 66 | needs: test 67 | name: Finish up 68 | 69 | runs-on: ubuntu-latest 70 | steps: 71 | - name: Coveralls Finished 72 | uses: coverallsapp/github-action@v1.1.2 73 | with: 74 | github-token: ${{ secrets.GITHUB_TOKEN }} 75 | parallel-finished: true 76 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | 8 | jobs: 9 | release-please: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: GoogleCloudPlatform/release-please-action@v2 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | release-type: node 16 | package-name: release-please-action 17 | bump-minor-pre-major: true 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Garbage files 64 | .DS_Store 65 | 66 | # Test results 67 | test.xunit 68 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | .nyc_output/ 3 | CHANGELOG.md 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.0.0](https://www.github.com/gulpjs/async-done/compare/v1.3.2...v2.0.0) (2022-06-25) 4 | 5 | 6 | ### ⚠ BREAKING CHANGES 7 | 8 | * Allow end-of-stream to handle the stream error states 9 | * Normalize repository, dropping node <10.13 support (#54) 10 | 11 | ### Bug Fixes 12 | 13 | * Allow end-of-stream to handle the stream error states ([7b37da4](https://www.github.com/gulpjs/async-done/commit/7b37da45e8344e78a5d40a8c277cc57d796c1257)) 14 | * Ensure the Observable failure test works ([#59](https://www.github.com/gulpjs/async-done/issues/59)) ([dfa4f0b](https://www.github.com/gulpjs/async-done/commit/dfa4f0b30b1c4666dbf6c930aac62434cf6a0c1c)) 15 | 16 | 17 | ### Miscellaneous Chores 18 | 19 | * Normalize repository, dropping node <10.13 support ([#54](https://www.github.com/gulpjs/async-done/issues/54)) ([66f987f](https://www.github.com/gulpjs/async-done/commit/66f987f36d2cbd07d5b96f487ea327caa44acb10)) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2019, 2021 Blaine Bublitz and Eric Schoffstall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | # async-done 8 | 9 | [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][ci-image]][ci-url] [![Coveralls Status][coveralls-image]][coveralls-url] 10 | 11 | Allows libraries to handle various caller provided asynchronous functions uniformly. Maps promises, observables, child processes and streams, and callbacks to callback style. 12 | 13 | As async conventions evolve, it is useful to be able to deal with several different _styles_ of async completion uniformly. With this module you can handle completion using a node-style callback, regardless of a return value that's a promise, observable, child process or stream. 14 | 15 | ## Usage 16 | 17 | ### Successful completion 18 | 19 | ```js 20 | var asyncDone = require('async-done'); 21 | 22 | asyncDone( 23 | function (done) { 24 | // do async things 25 | done(null, 2); 26 | }, 27 | function (error, result) { 28 | // `error` will be null on successful execution of the first function. 29 | // `result` will be the result from the first function. 30 | } 31 | ); 32 | ``` 33 | 34 | ### Failed completion 35 | 36 | ```js 37 | var asyncDone = require('async-done'); 38 | 39 | asyncDone( 40 | function (done) { 41 | // do async things 42 | done(new Error('Some Error Occurred')); 43 | }, 44 | function (error, result) { 45 | // `error` will be an error from the first function. 46 | // `result` will be undefined on failed execution of the first function. 47 | } 48 | ); 49 | ``` 50 | 51 | ## API 52 | 53 | ### `asyncDone(fn, callback)` 54 | 55 | Takes a function to execute (`fn`) and a function to call on completion (`callback`). 56 | 57 | #### `fn([done])` 58 | 59 | Optionally takes a callback to call when async tasks are complete. 60 | 61 | #### Completion and Error Resolution 62 | 63 | - `Callback` (`done`) called 64 | - Completion: called with null error 65 | - Error: called with non-null error 66 | - `Stream` or `EventEmitter` returned 67 | - Completion: [end-of-stream][end-of-stream] module 68 | - Error: [domains][domains] 69 | - **Note:** Only actual streams are supported, not faux-streams; Therefore, modules like [`event-stream`][event-stream] are not supported. 70 | - `Child Process` returned 71 | - Completion [end-of-stream][end-of-stream] module 72 | - Error: [domains][domains] 73 | - `Promise` returned 74 | - Completion: [onFulfilled][promise-onfulfilled] method called 75 | - Error: [onRejected][promise-onrejected] method called 76 | - `Observable` (e.g. from [RxJS v5][rxjs5-observable] or [RxJS v4][rxjs4-observable]) returned 77 | - Completion: [complete][rxjs5-observer-complete] method called 78 | - Error: [error][rxjs5-observer-error] method called 79 | 80 | **Warning:** Sync tasks are **not supported** and your function will never complete if the one of the above strategies is not used to signal completion. However, thrown errors will be caught by the domain. 81 | 82 | #### `callback(error, result)` 83 | 84 | If an error doesn't occur in the execution of the `fn` function, the `callback` method will receive the results as its second argument. Note: Some streams don't received any results. 85 | 86 | If an error occurred in the execution of the `fn` function, The `callback` method will receive an error as its first argument. 87 | 88 | Errors can be caused by: 89 | 90 | - A thrown error 91 | - An error passed to a `done` callback 92 | - An `error` event emitted on a returned `Stream`, `EventEmitter` or `Child Process` 93 | - A rejection of a returned `Promise` - If the `Promise` is not rejected with a value, we generate a new `Error` 94 | - The `onError` handler being called on an `Observable` 95 | 96 | ## License 97 | 98 | MIT 99 | 100 | 101 | [downloads-image]: https://img.shields.io/npm/dm/async-done.svg?style=flat-square 102 | [npm-url]: https://www.npmjs.com/package/async-done 103 | [npm-image]: https://img.shields.io/npm/v/async-done.svg?style=flat-square 104 | 105 | [ci-url]: https://github.com/gulpjs/async-done/actions?query=workflow:dev 106 | [ci-image]: https://img.shields.io/github/actions/workflow/status/gulpjs/async-done/dev.yml?branch=master&style=flat-square 107 | 108 | [coveralls-url]: https://coveralls.io/r/gulpjs/async-done 109 | [coveralls-image]: https://img.shields.io/coveralls/gulpjs/async-done/master.svg?style=flat-square 110 | 111 | 112 | 113 | [end-of-stream]: https://www.npmjs.com/package/end-of-stream 114 | [domains]: http://nodejs.org/api/domain.html 115 | [event-stream]: https://github.com/dominictarr/event-stream 116 | [promise-onfulfilled]: http://promisesaplus.com/#point-26 117 | [promise-onrejected]: http://promisesaplus.com/#point-30 118 | [rxjs4-observable]: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md 119 | [rxjs5-observable]: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html 120 | [rxjs5-observer-complete]: http://reactivex.io/rxjs/class/es6/MiscJSDoc.js~ObserverDoc.html#instance-method-complete 121 | [rxjs5-observer-error]: http://reactivex.io/rxjs/class/es6/MiscJSDoc.js~ObserverDoc.html#instance-method-error 122 | 123 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Notes about these type definitions: 3 | * 4 | * - Callbacks returning multiple completion values using multiple arguments are not supported by these types. 5 | * Prefer to use Node's style by grouping your values in a single object or array. 6 | * Support for this kind of callback is blocked by Microsoft/TypeScript#5453 7 | * 8 | * - For ease of use, `asyncDone` lets you pass callback functions with a result type `T` instead of `T | undefined`. 9 | * This matches Node's types but can lead to unsound code being typechecked. 10 | * 11 | * The following code typechecks but fails at runtime: 12 | * ```typescript 13 | * async function getString(): Promise { 14 | * return "Hello, World!"; 15 | * } 16 | * 17 | * async function evilGetString(): Promise { 18 | * throw new Error("Hello, World!"); 19 | * } 20 | * 21 | * function cb(err: Error | null, result: string): void { 22 | * // This is unsound because `result` is `undefined` when `err` is not `null`. 23 | * console.log(result.toLowerCase()); 24 | * } 25 | * 26 | * asyncDone(getString, cb); // Prints `hello, world!` 27 | * asyncDone(evilGetString, cb); // Runtime error: `TypeError: Cannot read property 'toLowerCase' of undefined` 28 | * ``` 29 | * 30 | * Enforcing stricter callbacks would require developers to use `result?: string` and assert the existence 31 | * of the result either by checking it directly or using the `!` assertion operator after testing for errors. 32 | * ```typescript 33 | * function stricterCb1(err: Error | null, result?: string): void { 34 | * if (err !== null) { 35 | * console.error(err); 36 | * return; 37 | * } 38 | * console.log(result!.toLowerCase()); 39 | * } 40 | * 41 | * function stricterCb2(err: Error | null, result?: string): void { 42 | * if (result === undefined) { 43 | * console.error("Undefined result. Error:); 44 | * console.error(err); 45 | * return; 46 | * } 47 | * console.log(result.toLowerCase()); 48 | * } 49 | * ``` 50 | */ 51 | import { ChildProcess } from 'child_process'; 52 | import { EventEmitter } from 'events'; 53 | import { Stream } from 'stream'; 54 | 55 | declare namespace asyncDone { 56 | /** 57 | * Represents a callback function used to signal the completion of a 58 | * task without any result value. 59 | */ 60 | type VoidCallback = (err: Error | null | void) => void; 61 | 62 | /** 63 | * Represents a callback function used to signal the completion of a 64 | * task with a single result value. 65 | */ 66 | interface Callback { 67 | (err: null, result: T): void; 68 | 69 | // Use `result?: T` or `result: undefined` to require the consumer to assert the existence of the result 70 | // (even in case of success). See comment at the top of the file. 71 | (err: Error, result?: any): void; 72 | } 73 | 74 | /** 75 | * Minimal `Observable` interface compatible with `async-done`. 76 | * 77 | * @see https://github.com/ReactiveX/rxjs/blob/c3c56867eaf93f302ac7cd588034c7d8712f2834/src/internal/Observable.ts#L77 78 | */ 79 | interface Observable { 80 | subscribe( 81 | next?: (value: T) => void, 82 | error?: (error: any) => void, 83 | complete?: () => void 84 | ): any; 85 | } 86 | 87 | /** 88 | * Represents an async operation. 89 | */ 90 | export type AsyncTask = 91 | | ((done: VoidCallback) => void) 92 | | ((done: Callback) => void) 93 | | (() => 94 | | ChildProcess 95 | | EventEmitter 96 | | Observable 97 | | PromiseLike 98 | | Stream); 99 | } 100 | 101 | /** 102 | * Takes a function to execute (`fn`) and a function to call on completion (`callback`). 103 | * 104 | * @param fn Function to execute. 105 | * @param callback Function to call on completion. 106 | */ 107 | declare function asyncDone( 108 | fn: asyncDone.AsyncTask, 109 | callback: asyncDone.Callback 110 | ): void; 111 | 112 | export = asyncDone; 113 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var domain = require('domain'); 4 | 5 | var eos = require('end-of-stream'); 6 | var once = require('once'); 7 | var exhaust = require('stream-exhaust'); 8 | 9 | var eosConfig = {}; 10 | 11 | function rethrowAsync(err) { 12 | process.nextTick(rethrow); 13 | 14 | function rethrow() { 15 | throw err; 16 | } 17 | } 18 | 19 | function tryCatch(fn, args) { 20 | try { 21 | return fn.apply(null, args); 22 | } catch (err) { 23 | rethrowAsync(err); 24 | } 25 | } 26 | 27 | function asyncDone(fn, cb) { 28 | cb = once(cb); 29 | 30 | var d = domain.create(); 31 | d.once('error', onError); 32 | var domainBoundFn = d.bind(fn); 33 | 34 | function done() { 35 | d.removeListener('error', onError); 36 | d.exit(); 37 | return tryCatch(cb, arguments); 38 | } 39 | 40 | function onSuccess(result) { 41 | done(null, result); 42 | } 43 | 44 | function onError(error) { 45 | if (!error) { 46 | error = new Error('Promise rejected without Error'); 47 | } 48 | done(error); 49 | } 50 | 51 | function asyncRunner() { 52 | var result = domainBoundFn(done); 53 | 54 | function onNext(state) { 55 | onNext.state = state; 56 | } 57 | 58 | function onCompleted() { 59 | onSuccess(onNext.state); 60 | } 61 | 62 | if (result && typeof result.on === 'function') { 63 | // Assume node stream 64 | d.add(result); 65 | eos(exhaust(result), eosConfig, done); 66 | return; 67 | } 68 | 69 | if (result && typeof result.subscribe === 'function') { 70 | // Assume RxJS observable 71 | result.subscribe(onNext, onError, onCompleted); 72 | return; 73 | } 74 | 75 | if (result && typeof result.then === 'function') { 76 | // Assume promise 77 | result.then(onSuccess, onError); 78 | return; 79 | } 80 | } 81 | 82 | process.nextTick(asyncRunner); 83 | } 84 | 85 | module.exports = asyncDone; 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-done", 3 | "version": "2.0.0", 4 | "description": "Allows libraries to handle various caller provided asynchronous functions uniformly. Maps promises, observables, child processes and streams, and callbacks to callback style.", 5 | "author": "Gulp Team (https://gulpjs.com/)", 6 | "contributors": [ 7 | "Blaine Bublitz ", 8 | "Pawel Kozlowski ", 9 | "Matthew Podwysocki ", 10 | "Charles Samborski " 11 | ], 12 | "repository": "gulpjs/async-done", 13 | "license": "MIT", 14 | "engines": { 15 | "node": ">= 10.13.0" 16 | }, 17 | "main": "index.js", 18 | "types": "index.d.ts", 19 | "files": [ 20 | "index.js", 21 | "index.d.ts", 22 | "LICENSE" 23 | ], 24 | "scripts": { 25 | "lint": "eslint .", 26 | "pretest": "npm run lint", 27 | "test": "nyc mocha --async-only", 28 | "test-types": "tsc -p test/types" 29 | }, 30 | "dependencies": { 31 | "end-of-stream": "^1.4.4", 32 | "once": "^1.4.0", 33 | "stream-exhaust": "^1.0.2" 34 | }, 35 | "devDependencies": { 36 | "@types/node": "^16.11.7", 37 | "eslint": "^7.32.0", 38 | "eslint-config-gulp": "^5.0.1", 39 | "eslint-plugin-node": "^11.1.0", 40 | "expect": "^27.3.1", 41 | "mocha": "^8.4.0", 42 | "nyc": "^15.1.0", 43 | "pumpify": "^2.0.1", 44 | "rxjs": "^7.4.0", 45 | "streamx": "^2.12.0", 46 | "through2": "^4.0.2", 47 | "typescript": "^4.4.4" 48 | }, 49 | "nyc": { 50 | "reporter": [ 51 | "lcov", 52 | "text-summary" 53 | ] 54 | }, 55 | "prettier": { 56 | "singleQuote": true 57 | }, 58 | "keywords": [ 59 | "promises", 60 | "callbacks", 61 | "observables", 62 | "streams", 63 | "end", 64 | "completion", 65 | "complete", 66 | "finish", 67 | "done", 68 | "async", 69 | "error handling" 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /test/arguments.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var asyncDone = require('../'); 6 | 7 | function twoArg(cb) { 8 | cb(null, 1, 2); 9 | } 10 | 11 | describe('arguments', function () { 12 | it('passes all arguments to the completion callback', function (done) { 13 | asyncDone(twoArg, function (err, arg1, arg2) { 14 | expect(arg1).toEqual(1); 15 | expect(arg2).toEqual(2); 16 | done(err); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var asyncDone = require('../'); 6 | 7 | function success(cb) { 8 | cb(null, 2); 9 | } 10 | 11 | function failure(cb) { 12 | cb(new Error('Callback Error')); 13 | } 14 | 15 | function neverDone() { 16 | return 2; 17 | } 18 | 19 | describe('callbacks', function () { 20 | it('should handle a successful callback', function (done) { 21 | asyncDone(success, function (err, result) { 22 | expect(result).toEqual(2); 23 | done(err); 24 | }); 25 | }); 26 | 27 | it('should handle an errored callback', function (done) { 28 | asyncDone(failure, function (err) { 29 | expect(err).toBeInstanceOf(Error); 30 | done(); 31 | }); 32 | }); 33 | 34 | it('a function that takes an argument but never calls callback', function (done) { 35 | asyncDone(neverDone, function () { 36 | done(new Error('Callback called')); 37 | }); 38 | 39 | setTimeout(function () { 40 | done(); 41 | }, 1000); 42 | }); 43 | 44 | it('should not handle error if something throws inside the callback', function (done) { 45 | var d = require('domain').create(); 46 | d.on('error', function (err) { 47 | expect(err).toBeInstanceOf(Error); 48 | done(); 49 | }); 50 | 51 | d.run(function () { 52 | asyncDone(success, function () { 53 | throw new Error('Thrown Error'); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/child_processes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var cp = require('child_process'); 6 | var asyncDone = require('../'); 7 | 8 | function execSuccess() { 9 | return cp.exec('echo hello world'); 10 | } 11 | 12 | function execFail() { 13 | return cp.exec('foo-bar-baz hello world'); 14 | } 15 | 16 | function spawnSuccess() { 17 | return cp.spawn('echo', ['hello world']); 18 | } 19 | 20 | function spawnFail() { 21 | return cp.spawn('foo-bar-baz', ['hello world']); 22 | } 23 | 24 | describe('child processes', function () { 25 | it('should handle successful exec', function (done) { 26 | asyncDone(execSuccess, function (err) { 27 | expect(err).not.toBeInstanceOf(Error); 28 | done(); 29 | }); 30 | }); 31 | 32 | it('should handle failing exec', function (done) { 33 | asyncDone(execFail, function (err) { 34 | expect(err).toBeInstanceOf(Error); 35 | done(); 36 | }); 37 | }); 38 | 39 | it('should handle successful spawn', function (done) { 40 | asyncDone(spawnSuccess, function (err) { 41 | expect(err).not.toBeInstanceOf(Error); 42 | done(); 43 | }); 44 | }); 45 | 46 | it('should handle failing spawn', function (done) { 47 | asyncDone(spawnFail, function (err) { 48 | expect(err).toBeInstanceOf(Error); 49 | done(); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/observables.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var asyncDone = require('../'); 6 | 7 | var rxjs = require('rxjs'); 8 | 9 | function success() { 10 | return rxjs.empty(); 11 | } 12 | 13 | function successValue() { 14 | // This corresponds to `Observable.return(42);` in RxJS 4 15 | return rxjs.of(42); 16 | } 17 | 18 | function failure() { 19 | return rxjs.throwError(function () { 20 | return new Error('Observable error'); 21 | }); 22 | } 23 | 24 | describe('observables', function () { 25 | it('should handle a finished observable', function (done) { 26 | asyncDone(success, function (err, result) { 27 | expect(result).toBeUndefined(); 28 | done(err); 29 | }); 30 | }); 31 | 32 | it('should handle a finished observable with value', function (done) { 33 | asyncDone(successValue, function (err, result) { 34 | expect(result).toEqual(42); 35 | done(err); 36 | }); 37 | }); 38 | 39 | it('should handle an errored observable', function (done) { 40 | asyncDone(failure, function (err) { 41 | expect(err).toBeInstanceOf(Error); 42 | expect(err.message).toEqual('Observable error'); 43 | done(); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/promises.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var domain = require('domain'); 4 | 5 | var expect = require('expect'); 6 | 7 | var asyncDone = require('../'); 8 | 9 | function success() { 10 | return Promise.resolve(2); 11 | } 12 | 13 | function failure() { 14 | return Promise.reject(new Error('Promise Error')); 15 | } 16 | 17 | function rejectNoError() { 18 | return Promise.reject(); 19 | } 20 | 21 | describe('promises', function () { 22 | it('should handle a resolved promise', function (done) { 23 | asyncDone(success, function (err, result) { 24 | expect(result).toEqual(2); 25 | done(err); 26 | }); 27 | }); 28 | 29 | it('should handle a rejected promise', function (done) { 30 | asyncDone(failure, function (err) { 31 | expect(err).toBeInstanceOf(Error); 32 | done(); 33 | }); 34 | }); 35 | 36 | it('properly errors when rejected without an error', function (done) { 37 | asyncDone(rejectNoError, function (err) { 38 | expect(err).toBeTruthy(); 39 | expect(err).toBeInstanceOf(Error); 40 | done(); 41 | }); 42 | }); 43 | 44 | it('does not swallow thrown errors in callback', function (done) { 45 | var d = domain.create(); 46 | d.once('error', function (err) { 47 | expect(err).toBeTruthy(); 48 | expect(err.message).toContain('Boom'); 49 | done(); 50 | }); 51 | d.run(function () { 52 | asyncDone(success, function () { 53 | throw new Error('Boom'); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/streams.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var through = require('through2'); 8 | var pumpify = require('pumpify'); 9 | 10 | var asyncDone = require('../'); 11 | 12 | var exists = path.join(__dirname, '../.gitignore'); 13 | var notExists = path.join(__dirname, '../not_exists'); 14 | 15 | var EndStream = through.ctor( 16 | function (chunk, enc, cb) { 17 | this.push(chunk); 18 | cb(); 19 | }, 20 | function (cb) { 21 | this.emit('end', 2); 22 | cb(); 23 | } 24 | ); 25 | 26 | function success() { 27 | var read = fs.createReadStream(exists); 28 | return read.pipe(new EndStream()); 29 | } 30 | 31 | function failure() { 32 | var read = fs.createReadStream(notExists); 33 | return read.pipe(new EndStream()); 34 | } 35 | 36 | function withErr(chunk, _, cb) { 37 | cb(new Error('Fail')); 38 | } 39 | 40 | function pumpifyError() { 41 | var read = fs.createReadStream(exists); 42 | var pipeline = pumpify(through(), through(withErr), through()); 43 | 44 | return read.pipe(pipeline); 45 | } 46 | 47 | function unpiped() { 48 | return fs.createReadStream(exists); 49 | } 50 | 51 | describe('streams', function () { 52 | it('should handle a successful stream', function (done) { 53 | asyncDone(success, function (err) { 54 | expect(err).not.toBeInstanceOf(Error); 55 | done(); 56 | }); 57 | }); 58 | 59 | it('should handle an errored stream', function (done) { 60 | asyncDone(failure, function (err) { 61 | expect(err).toBeInstanceOf(Error); 62 | done(); 63 | }); 64 | }); 65 | 66 | it('should handle an errored pipeline', function (done) { 67 | asyncDone(pumpifyError, function (err) { 68 | expect(err).toBeInstanceOf(Error); 69 | expect(err.message).not.toEqual('premature close'); 70 | done(); 71 | }); 72 | }); 73 | 74 | it('handle a returned stream and cb by only calling callback once', function (done) { 75 | asyncDone( 76 | function (cb) { 77 | return success().on('end', function () { 78 | cb(null, 3); 79 | }); 80 | }, 81 | function (err, result) { 82 | expect(err).not.toBeInstanceOf(Error); 83 | expect(result).toEqual(3); // To know we called the callback 84 | done(); 85 | } 86 | ); 87 | }); 88 | 89 | it('consumes an unpiped readable stream', function (done) { 90 | asyncDone(unpiped, function (err) { 91 | expect(err).not.toBeInstanceOf(Error); 92 | done(); 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /test/streamx.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var streamx = require('streamx'); 6 | 7 | var asyncDone = require('../'); 8 | 9 | function success() { 10 | return streamx.Readable.from('Foo Bar Baz').pipe(new streamx.Writable()); 11 | } 12 | 13 | function failure() { 14 | return streamx.Readable.from('Foo Bar Baz').pipe( 15 | new streamx.Writable({ 16 | write: function (data, cb) { 17 | cb(new Error('Fail')); 18 | }, 19 | }) 20 | ); 21 | } 22 | 23 | function pipelineError() { 24 | return streamx.pipeline( 25 | streamx.Readable.from('Foo Bar Baz'), 26 | new streamx.Transform(), 27 | new streamx.Transform({ 28 | transform: function (data, cb) { 29 | cb(new Error('Fail')); 30 | }, 31 | }), 32 | new streamx.Writable() 33 | ); 34 | } 35 | 36 | function unpiped() { 37 | return streamx.Readable.from('Foo Bar Baz'); 38 | } 39 | 40 | describe('streamx streams', function () { 41 | it('should handle a successful stream', function (done) { 42 | asyncDone(success, function (err) { 43 | expect(err).not.toBeInstanceOf(Error); 44 | done(); 45 | }); 46 | }); 47 | 48 | it('should handle an errored stream', function (done) { 49 | asyncDone(failure, function (err) { 50 | expect(err).toBeInstanceOf(Error); 51 | expect(err.message).not.toEqual('premature close'); 52 | done(); 53 | }); 54 | }); 55 | 56 | it('should handle an errored pipeline', function (done) { 57 | asyncDone(pipelineError, function (err) { 58 | expect(err).toBeInstanceOf(Error); 59 | expect(err.message).not.toEqual('premature close'); 60 | done(); 61 | }); 62 | }); 63 | 64 | it('handle a returned stream and cb by only calling callback once', function (done) { 65 | asyncDone( 66 | function (cb) { 67 | return success().on('finish', function () { 68 | cb(null, 3); 69 | }); 70 | }, 71 | function (err, result) { 72 | expect(err).not.toBeInstanceOf(Error); 73 | expect(result).toEqual(3); // To know we called the callback 74 | done(); 75 | } 76 | ); 77 | }); 78 | 79 | it('consumes an unpiped readable stream', function (done) { 80 | asyncDone(unpiped, function (err) { 81 | expect(err).not.toBeInstanceOf(Error); 82 | done(); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/types/callback.ts: -------------------------------------------------------------------------------- 1 | import asyncDone, { Callback } from 'async-done'; 2 | 3 | function success(cb: Callback): void { 4 | cb(null, 2); 5 | } 6 | 7 | function failure(cb: Callback): void { 8 | cb(new Error('Callback Error')); 9 | } 10 | 11 | function neverDone(): number { 12 | return 2; 13 | } 14 | 15 | // `success` and stricter callback 16 | asyncDone(success, function (err: Error | null, result?: number): void { 17 | console.log('Done'); 18 | }); 19 | 20 | // The following code fails to compile as expected: 21 | // asyncDone(success, function (err: Error | null, result?: string): void { 22 | // console.log("Done"); 23 | // }); 24 | 25 | // `success` and unsound callback 26 | asyncDone(success, function (err: Error | null, result: number): void { 27 | console.log('Done'); 28 | }); 29 | 30 | // `failure` and stricter callback 31 | asyncDone(failure, function (err: Error | null, result?: number): void { 32 | console.log('Done'); 33 | }); 34 | 35 | // `failure` and unsound callback 36 | asyncDone(failure, function (err: Error | null, result: number): void { 37 | console.log('Done'); 38 | }); 39 | 40 | // I don't think TS is currently able to prevent the current code from compiling 41 | // (`neverDone` matches with `(done: VoidCallback) => void` for example) 42 | // asyncDone(neverDone, function(err: Error | null, result?: number): void { 43 | // console.log("Done"); 44 | // }); 45 | -------------------------------------------------------------------------------- /test/types/child_processes.ts: -------------------------------------------------------------------------------- 1 | import asyncDone from 'async-done'; 2 | import cp from 'child_process'; 3 | 4 | function success(): cp.ChildProcess { 5 | return cp.exec('echo hello world'); 6 | } 7 | 8 | function failure(): cp.ChildProcess { 9 | return cp.exec('foo-bar-baz hello world'); 10 | } 11 | 12 | asyncDone(success, function (err: Error | null): void { 13 | console.log('Done'); 14 | }); 15 | 16 | asyncDone(failure, function (err: Error | null): void { 17 | console.log('Done'); 18 | }); 19 | -------------------------------------------------------------------------------- /test/types/observables.ts: -------------------------------------------------------------------------------- 1 | import asyncDone from 'async-done'; 2 | import { empty, of, throwError, Observable } from 'rxjs'; 3 | import 'rxjs/add/observable/empty'; 4 | import 'rxjs/add/observable/of'; 5 | 6 | function success(): Observable { 7 | return empty(); 8 | } 9 | 10 | function successValue(): Observable { 11 | return of(42); 12 | } 13 | 14 | function failure(): Observable { 15 | return throwError(new Error('Observable error')); 16 | } 17 | 18 | // `success` callback 19 | asyncDone(success, function (err: Error | null): void { 20 | console.log('Done'); 21 | }); 22 | 23 | // The following code fails to compile as expected (`undefined` is not assignable to `number`): 24 | // asyncDone(success, function (err: Error | null, result: number): void { 25 | // console.log("Done"); 26 | // }); 27 | 28 | // `successValue` and stricter callback 29 | asyncDone(successValue, function (err: Error | null, result?: number): void { 30 | console.log('Done'); 31 | }); 32 | 33 | // `successValue` and unsound callback 34 | asyncDone(successValue, function (err: Error | null, result: number): void { 35 | console.log('Done'); 36 | }); 37 | 38 | // `failure` and stricter callback 39 | asyncDone(failure, function (err: Error | null, result?: number): void { 40 | console.log('Done'); 41 | }); 42 | 43 | // `failure` and unsound callback 44 | asyncDone(failure, function (err: Error | null, result: number): void { 45 | console.log('Done'); 46 | }); 47 | -------------------------------------------------------------------------------- /test/types/promises.ts: -------------------------------------------------------------------------------- 1 | import asyncDone from 'async-done'; 2 | 3 | function success(): Promise { 4 | return Promise.resolve(2); 5 | } 6 | 7 | function failure(): Promise { 8 | return Promise.reject(new Error('Promise Error')); 9 | } 10 | 11 | // `successValue` and stricter callback 12 | asyncDone(success, function (err: Error | null, result?: number): void { 13 | console.log('Done'); 14 | }); 15 | 16 | // The following code fails to compile as expected: 17 | // asyncDone(success, function (err: Error | null, result?: string): void { 18 | // console.log("Done"); 19 | // }); 20 | 21 | // `successValue` and unsound callback 22 | asyncDone(success, function (err: Error | null, result: number): void { 23 | console.log('Done'); 24 | }); 25 | 26 | // `failure` and stricter callback 27 | asyncDone(failure, function (err: Error | null, result?: number): void { 28 | console.log('Done'); 29 | }); 30 | 31 | // `failure` and unsound callback 32 | asyncDone(failure, function (err: Error | null, result: number): void { 33 | console.log('Done'); 34 | }); 35 | -------------------------------------------------------------------------------- /test/types/streams.ts: -------------------------------------------------------------------------------- 1 | import asyncDone from 'async-done'; 2 | import { Readable, Stream } from 'stream'; 3 | 4 | function streamSuccess(): Stream { 5 | return new Stream(); 6 | } 7 | 8 | function streamFail(): Stream { 9 | return new Stream(); 10 | } 11 | 12 | asyncDone(streamSuccess, function (err: Error | null): void { 13 | console.log('Done'); 14 | }); 15 | 16 | asyncDone(streamFail, function (err: Error | null): void { 17 | console.log('Done'); 18 | }); 19 | -------------------------------------------------------------------------------- /test/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "allowSyntheticDefaultImports": true, 5 | "allowUnreachableCode": false, 6 | "allowUnusedLabels": false, 7 | "alwaysStrict": true, 8 | "baseUrl": "../..", 9 | "charset": "utf8", 10 | "checkJs": false, 11 | "declaration": true, 12 | "disableSizeLimit": false, 13 | "downlevelIteration": false, 14 | "emitBOM": false, 15 | "emitDecoratorMetadata": true, 16 | "experimentalDecorators": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "importHelpers": false, 19 | "inlineSourceMap": false, 20 | "inlineSources": false, 21 | "isolatedModules": false, 22 | "lib": ["es2017"], 23 | "locale": "en-us", 24 | "module": "commonjs", 25 | "moduleResolution": "node", 26 | "newLine": "lf", 27 | "noEmit": true, 28 | "noEmitHelpers": false, 29 | "noEmitOnError": true, 30 | "noErrorTruncation": true, 31 | "noFallthroughCasesInSwitch": true, 32 | "noImplicitAny": true, 33 | "noImplicitReturns": true, 34 | "noImplicitThis": true, 35 | "noStrictGenericChecks": false, 36 | "noUnusedLocals": false, 37 | "noUnusedParameters": false, 38 | "noLib": false, 39 | "noResolve": false, 40 | "paths": { 41 | "async-done": ["index.d.ts"] 42 | }, 43 | "preserveConstEnums": true, 44 | "removeComments": false, 45 | "rootDir": "", 46 | "skipLibCheck": false, 47 | "sourceMap": true, 48 | "strict": true, 49 | "strictNullChecks": true, 50 | "suppressExcessPropertyErrors": false, 51 | "suppressImplicitAnyIndexErrors": false, 52 | "target": "es2017", 53 | "traceResolution": false 54 | }, 55 | "include": ["./**/*.ts"], 56 | "exclude": [] 57 | } 58 | -------------------------------------------------------------------------------- /test/types/various.ts: -------------------------------------------------------------------------------- 1 | import asyncDone, { AsyncTask, VoidCallback } from 'async-done'; 2 | 3 | // Do not error if the return value is not `void`. 4 | const fn: AsyncTask = (cb: VoidCallback): NodeJS.Timer => setTimeout(cb, 1000); 5 | asyncDone(fn, () => {}); 6 | --------------------------------------------------------------------------------