The defining characteristic of node.js, and a primary reason for its success, is its asynchronous "non-blocking" IO (input/output) APIs. In the case of JavaScript, asynchronous means the value will be processed at a later tick of the event loop:
// Note, all of the below ignore errors
console.log(1)
// async/await version
async ()=>{
console.log(await fs.promise.readdir(__dirname))
}()
// Promise version
fs.promise.readdir(__dirname)
.then(files => console.log(files))
// Callback version
fs.readdir(__dirname, (err, files) => console.log(files))
console.log(2)
/* Output:
1
2
['index.js']
['index.js']
['index.js']
*/
Note: The node.js core APIs, by default, use callbacks. These guides will assume you are using songbird to expose the promise = module.promise.method(...) helper API (e.g., promise = fs.promise.readdir(...)).
async/awaitAsync functions were added to JavaScript in ES7, giving us the ability to use a synchronous-style syntax for asynchronous programming.
Functions marked with the async keyword, can use await within their body:
async ()=> {
console.log(await fs.promise.readdir(filename)) // ['index.js']
}
await pauses the execution of the current stack frame until the asynchronous operation (aka Promise) resolves.
asyncThe async keyword is used to create an asynchronous function.
async ()=> {
// ...
}
In almost every way, async functions behave exactly like regular functions. They can be passed arguments, return values, named, nested, invoked, invoked immediately, inherit from Function.prototype, etc. However, they differ in a few ways.
async functions:
Promise that
return valueawait within the shallow function body (not in nested functions)async function add(one, two) {
await process.promise.nextTick()
return one + two
}
let promise = add(1, 2)
promise.then(result => console.log(result)) // 3
awaitUse await to wait for a Promise and its resolution value:
async ()=>{
let filenames = await fs.promise.readdir(__dirname)
console.log(filenames) // ['index.js']
}()
If the promise fails, an error will be thrown instead:
async ()=>{
let filenames = await fs.promise.readdir(' does not exist')
}().catch(e => console.log(e)) // Error: ENOENT: no such file or directory
Further, await conveniently allows us to use language control-flow features like for, if and try/catch when writing asynchronous code:
async function ls(dirPath) {
let stat = await fs.promise.stat(dirPath)
if (!stat.isDirectory()) return [dirPath]
let filenames = []
for (let name of await fs.promise.readdir(dirPath)) {
let result = await ls(path.join(dirPath, name))
filenames.push(...result)
}
return filenames
}
async ()=> {
try {
console.log(await ls(__dirname))
} catch(e) {
console.log(e.stack)
}
}()
Added to the language in ES6, Promise API will be the standard idiom (in combination with async/await) for writing asynchronous JavaScript going forward.
// Consuming a promise API
let promise = fs.promise.readFile(__filename)
promise.then(result => {}, err => {})
// Returning promises
readFile(name) {
return fs.promise.readFile(path.join(__dirname, name))
}
readFile('README.md')
.then(result => {}, err => {})
Note: The Promise spec makes guarantees that are encapsulated within the API. Unlink callbacks, you need not worry about things like remembering to pass errors, never calling callbacks more than once or whether a control-flow library is compatible with those expectations. There's even a test suite to verify Promise spec compliance.
.then(onFulfilled, onRejected)newPromise = promise.then(result=>{}, err=>{}) is the primary promise API.
fs.promise.readFile(__filename)
.then(function onFulfilled(result) {
console.log(String(result)),
},
function onRejected(err) {
console.log(err.stack)
})
It's important to note, .then returns a new promise based on the following logic:
onFulfilled and onRejected are optional
promise is fulfilled, call onFulfilled with the result
promise is rejected, call onRejected with the error
promise will either be fulfilled or rejected, never both
promise will call onFulfilled or onRejected only once per .then()
promise.then() returns a new Promise, newPromise
newPromise resolves to an error if onFulfilled or onRejected throwsnewPromise resolves to the return value of onFulfilled or onRejected
promise is fulfilled, it will skip all onRejected to the next onFulfilled
promise is rejected, it will skip all onFulfilled to the next onRejected
Promise.resolve(1)
.then(res => ++res)
.then(res => ++res)
.then(console.log) // 3
// Skipping onFulfilled/onRejected
Promise.reject(new Error('fail'))
.then(() => console.log('hello')) // Never executes
.then(null, err => err.message) // Convert rejection to fulfillment
.then(null, console.log) // Never executes
.then(console.log) // 'fail'
Recommended: To get more comfortable with Promises, checkout the Nodeschool.io Promise It Won't Hurt Workshopper.
.catch(onRejected).catch(onRejected) is a short-hand for .then(null, onRejected)
Promise.all([promises])Converts an array of promises to a single promise that is fulfilled when all the promises are fulfilled, and is rejected otherwise.
let promises = [Promise.resolve(1), Promise.resolve(2)]
Promise.all(promises)
.then(console.log) // 1, 2
let promises = [Promise.resolve(1), Promise.reject(new Error('fail')]
Promise.all(promises)
.then(console.log) // Never executes
.catch(console.log) // 'fail'
Noted: Use Promise.all to wait on promises in parallel. This is especially useful when combined with await:
async function ls(dirPath) {
let stat = await fs.promise.stat(dirPath)
if (!stat.isDirectory()) return [dirPath]
let promises = []
for (let name of await fs.promise.readdir(dirPath)) {
// Run recursive ls in parallel
let promise = ls(path.join(dirPath, name))
promises.push(promise)
}
// Wait for them in parallel
return _.flatten(await promises)
}
async ()=> {
try {
console.log(await ls(__dirname))
} catch(e) {
console.log(e.stack)
}
}()
The node.js core APIs, by default, use callbacks.
Note: Node.js callbacks are sometimes referred to as "errbacks", "error-backs", "node-backs" or "error-first callbacks" to denote the specific flavor of callbacks in node.js.
Simple Description:
(err, result) => {}
fs.readFile(__filename, (err, result) => {} )
Simple right? But beware, "Here be dragons!"
When writing callbacks, there are numerous unenforced implicit expectations.
Here is the complete Callback Contract:
error instanceof Error === trueWhenever you write a callback, you must remember to follow all of the above requirements or else difficult-to-debug bugs may occur!
You will quickly find this to be both tedious and error-prone. As such, callbacks are by definition anti-patterns and are highly discouraged despite their mass adoption.
When dealing with asynchronous operations, sometimes you must wait on the previous operation's result. Other times, you do not. Sometimes you only care about the end result of a chain of operations. Below are examples of how to handle these scenarios.
Executing 3 tasks in series:
// async/await
async ()=>{
let a = await one()
let b = a + await two()
let c = b + await three()
console.log(c / 2)
}()
// promises
one()
.then(a => two().then(res => res + a))
.then(b => three().then(res => res + b))
.then(c => console.log(c / 2))
// callbacks + async package
async.waterfall([
(callback) => one(callback),
(a, callback) => two((err, res) => callback(err, res + a)),
(b, callback) => three((err, res) => callback(err, res + b)),
(c, callback) => console.log(c / 2)
])
Executing 3 tasks in parallel:
// async/await
async ()=>{
let [a, b, c] = await Promise.all([
one(),
two(),
three()
])
console.log(a + b + c)
}()
// promises + bluebird (for .spread)
Promise.all([
one(),
two(),
three()
]).spread((a, b, c) => console.log(a + b + c))
// callbacks + async package
async.parallel([
(callback) => one(callback),
(callback) => two(callback),
(callback) => three(callback)
], (err, res) => console.log(res[0] + res[1] + res[2]))
Branch is a term used to describe an independent chain of control-flow that results in a single value / outcome. Branches tend to correlate well with moving logic to separate functions.
// async/await
async ()=>{
// some stuff in series
let promiseX = async()=>{
let a = await one()
let b = await two()
return await three()
}
let promiseY = fs.promise.readdir(__dirname)
// some stuff in parallel
let [x, y] = await Promise.all([promiseX, promiseY])
console.log(x + y)
}()
// promises
// some stuff in series
let promiseX = one()
.then(a => two())
.then(b => three())
let promiseY = fs.promise.readdir(__dirname)
// some stuff in parallel
let [x, y] = await Promise.all([promiseX, promiseY])
console.log(x + y)
// callbacks + async package
// some stuff in parallel
async.parallel([
(callback) => {
// some stuff in series
async.waterfall([
(callback) => one(callback),
(a, callback) => two(callback),
(b, callback) => three(callback)
], callback)
},
(callback) => fs.readdir(callback)
], (err, res) => console.log(res[0] + res[1]))
You may not find yourself in all of these scenarios, but they're here for reference.
// Scenario 1: Named function
async function foo() {
// logic
}
promise = foo()
// Scenario 2: Lambda
promise = async ()=>{
// logic
}()
async fooAsync() {
return await fooPromiseReturning()
}
Using bluebird-nodeify:
async function fnAsync() {
// some logic
}
nodeify(asyncFn(), callback)
Using songbird:
async function asyncFn() {
// Callback -> Promise
return await fs.promise.readFile(...)
}
Using bluebird-nodeify:
nodeify(promise, callback)
Using songbird:
promise = fs.promise.readFile(...)
You can use async/await semantics in node.js today without babel by using yield and "async" Generator Functions (available in v0.12) in combination with APIs like bluebird.coroutine or co and co.wrap(function*(){...}):
$ # Turn on generator support
$ node --harmony --use-strict file-that-uses-generators.js
// Scenario 1: Reusable function*
let fnAsync = co.wrap(function *() {
// yield on promises like async/await
let files = yield fs.promise.readdir(__dirname)
})
let promise = fnAsync()
// Scenario 2: Lambda
let promise = co(function *() {
// yield on promises like async/await
let files = yield fs.promise.readdir(__dirname)
})
Note: In order of recommendation
promise-it-wont-hurt - Promise API workshopperlearn-generators - Async generators are an ES6 hack for writing ES7 async functions (will teach async/await but with function*/yield instead)async-you - Guide to using the most popular callback control-flow library, async
asyncawait - Callback heaven with async/await async/await in JavaScriptasync/await: The Hero JavaScript DeservedPromise mistakes explained