|
| 1 | +<!-- front-matter |
| 2 | +id: async-completion |
| 3 | +title: Async Completion |
| 4 | +hide_title: true |
| 5 | +sidebar_label: Async Completion |
| 6 | +--> |
| 7 | + |
| 8 | +# Async Completion |
| 9 | + |
| 10 | +Node libraries handle asynchronicity in a variety of ways. The most common pattern is [error-first callbacks][node-api-error-first-callbacks], but you might also encounter [streams][stream-docs], [promises][promise-docs], [event emitters][event-emitter-docs], [child processes][child-process-docs], or [observables][observable-docs]. Gulp tasks normalize all these types of asynchronicity. |
| 11 | + |
| 12 | +## Signal task completion |
| 13 | + |
| 14 | +When a stream, promise, event emitter, child process, or observable is returned from a task, the success or error informs gulp whether to continue or end. If a task errors, gulp will end immediately and show that error. |
| 15 | + |
| 16 | +When composing tasks with `series()`, an error will end the composition and no further tasks will be executed. When composing tasks with `parallel()`, an error will end the composition but the other parallel tasks may or may not complete. |
| 17 | + |
| 18 | +### Returning a stream |
| 19 | + |
| 20 | +```js |
| 21 | +const { src, dest } = require('gulp'); |
| 22 | + |
| 23 | +function streamTask() { |
| 24 | + return src('*.js') |
| 25 | + .pipe(dest('output')); |
| 26 | +} |
| 27 | + |
| 28 | +exports.default = streamTask; |
| 29 | +``` |
| 30 | + |
| 31 | +### Returning a promise |
| 32 | + |
| 33 | +```js |
| 34 | +function promiseTask() { |
| 35 | + return Promise.resolve('some ignored value'); |
| 36 | +} |
| 37 | + |
| 38 | +exports.default = promiseTask; |
| 39 | +``` |
| 40 | + |
| 41 | +### Returning an event emitter |
| 42 | + |
| 43 | +```js |
| 44 | +const { EventEmitter } = require('events'); |
| 45 | + |
| 46 | +function eventEmitterTask() { |
| 47 | + const emitter = new EventEmitter(); |
| 48 | + // Emit has to happen async otherwise gulp isn't listening yet |
| 49 | + setTimeout(() => emitter.emit('finish'), 250); |
| 50 | + return emitter; |
| 51 | +} |
| 52 | + |
| 53 | +exports.default = eventEmitterTask; |
| 54 | +``` |
| 55 | + |
| 56 | +### Returning a child process |
| 57 | + |
| 58 | +```js |
| 59 | +const { exec } = require('child_process'); |
| 60 | + |
| 61 | +function childProcessTask() { |
| 62 | + return exec('date'); |
| 63 | +} |
| 64 | + |
| 65 | +exports.default = childProcessTask; |
| 66 | +``` |
| 67 | + |
| 68 | +### Returning an observable |
| 69 | + |
| 70 | +```js |
| 71 | +const { Observable } = require('rxjs'); |
| 72 | + |
| 73 | +function observableTask() { |
| 74 | + return Observable.of(1, 2, 3); |
| 75 | +} |
| 76 | + |
| 77 | +exports.default = observableTask; |
| 78 | +``` |
| 79 | + |
| 80 | +### Using an error-first callback |
| 81 | + |
| 82 | +If nothing is returned from your task, you must use the error-first callback to signal completion. The callback will be passed to your task as the only argument - named `done()` in the examples below. |
| 83 | + |
| 84 | +```js |
| 85 | +function callbackTask(done) { |
| 86 | + // `done()` should be called by some async work |
| 87 | + done(); |
| 88 | +} |
| 89 | + |
| 90 | +exports.default = callbackTask; |
| 91 | +``` |
| 92 | + |
| 93 | +To indicate to gulp that an error occurred in a task using an error-first callback, call it with an `Error` as the only argument. |
| 94 | + |
| 95 | +```js |
| 96 | +function callbackError(done) { |
| 97 | + // `done()` should be called by some async work |
| 98 | + done(new Error('kaboom')); |
| 99 | +} |
| 100 | + |
| 101 | +exports.default = callbackError; |
| 102 | +``` |
| 103 | + |
| 104 | +However, you'll often pass this callback to another API instead of calling it yourself. |
| 105 | + |
| 106 | +```js |
| 107 | +const fs = require('fs'); |
| 108 | + |
| 109 | +function passingCallback(done) { |
| 110 | + fs.access('gulpfile.js', done); |
| 111 | +} |
| 112 | + |
| 113 | +exports.default = passingCallback; |
| 114 | +``` |
| 115 | + |
| 116 | +## No synchronous tasks |
| 117 | + |
| 118 | +Synchronous tasks are no longer supported. They often led to subtle mistakes that were hard to debug, like forgetting to return your streams from a task. |
| 119 | + |
| 120 | +When you see the _"Did you forget to signal async completion?"_ warning, none of the techniques mentioned above were used. You'll need to use the error-first callback or return a stream, promise, event emitter, child process, or observable to resolve the issue. |
| 121 | + |
| 122 | +## Using async/await |
| 123 | + |
| 124 | +When not using any of the previous options, you can define your task as an [`async` function][async-await-docs], which wraps your task in a promise. This allows you to work with promises synchronously using `await` and use other synchronous code. |
| 125 | + |
| 126 | +```js |
| 127 | +const fs = require('fs'); |
| 128 | + |
| 129 | +async function asyncAwaitTask() { |
| 130 | + const { version } = fs.readFileSync('package.json'); |
| 131 | + console.log(version); |
| 132 | + await Promise.resolve('some result'); |
| 133 | +} |
| 134 | + |
| 135 | +exports.default = asyncAwaitTask; |
| 136 | +``` |
| 137 | + |
| 138 | +[node-api-error-first-callbacks]: https://nodejs.org/api/errors.html#errors_error_first_callbacks |
| 139 | +[stream-docs]: https://nodejs.org/api/stream.html#stream_stream |
| 140 | +[promise-docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises |
| 141 | +[event-emitter-docs]: https://nodejs.org/api/events.html#events_events |
| 142 | +[child-process-docs]: https://nodejs.org/api/child_process.html#child_process_child_process |
| 143 | +[observable-docs]: https://github.com/tc39/proposal-observable/blob/master/README.md |
| 144 | +[async-await-docs]: https://developers.google.com/web/fundamentals/primers/async-functions |
0 commit comments