从 Callback Hell 到 Promise 到 Async/Await
【知乎 Live】狼叔:如何正确的学习 Node.js
Node.js 异步流程控制的学习重点:
- API 写法:Error-first Callback 和 EventEmitter;
- 中流砥柱:Promise;
- 终极解决方案:Async/Await
回调地狱(Callback Hell )
Node.js 默认使用回调函数(callback)风格语法来实现异步编程:
// Node.js 默认的回调函数风格写法,Callback Hell
step1(function(value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value 4.
});
});
});
});
// ES6 箭头函数语法:
step1(value1 => {
step2(value1, value2 => {
step3(value2, value3 => {
step4(value3, value4 => {
// Do something with value4.
});
});
});
});
Promise 语法
return step1().then(step2).then(step3).then(step4).catch(function(err) {
// do something with error.
});
InfoQ:异步 JavaScript 的演化史:从回调到 Promise 再到 Async/Await ⭐️⭐️⭐️⭐️
什么是回调函数(callback)?
首先,就像可以将字符串或数字以参数的形式传递给函数一样,我们也可以将函数的引用作为参数进行传递。但我们这样做的时候,作为参数传递的函数被称为回调函数(callback function),而接收回调函数传入的那个函数则被称为高阶函数(higher order function)。
function add(x, y) {
return x + y;
}
function higherOrderFunction(x, callback) {
return callback(x, 5);
}
higherOrderFunction(10, add);
回调的使用场景:将函数的执行延迟至一个特定的时间。
Promise
Promise
会处于如下三种状态中的某一种: pending
、fulfilled
或rejected
。
如果异步请求依然还在进行,那么Promise
的状态会是pending
。如果异步请求成功完成的话,那么Promise
会将状态转换为fulfilled
。如果异步请求失败的话,Promise
会将状态转换为rejected
。
Promise
的构造函数会接收一个参数,这个参数是一个(回调)函数。该函数会被传入两个参数resolve
和reject
。
resolve
:一个能将 Promise 状态变为fulfilled
的函数;
reject
:一个能将 Promise 状态变为rejected
的函数;
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(); // 将状态变为"fulfilled"
}, 2000);
});
当 Promise 的状态变为fulfilled
的时候,传递给.then
的函数将会被调用。如果 Promise 的状态变为rejected
,传递给.catch
的函数将会被调用。
function onSuccess() {
console.log('Success');
}
function onError() {
console.log('Error');
}
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(); // 将状态变为"fulfilled"
}, 2000);
});
promise.then(onSuccess);
promise.catch(onError);
Promise 的链式语法。
Async/Await
如何完全按照编写同步代码的方式来编写异步代码?
- 首先,只要你为函数添加
async
,它就会隐式的返回一个 Promise 对象:
async function getPromise() {}
const promise = getPromise();
- 其次,如果
async
函数有返回值的话,它也将会包装到一个 Promise 中。这意味着,你必须要使用.then
来访问它。
async function add(x, y) {
return x + y;
}
add(2, 3).then(result => {
console.log(result); // 5
});
-
不能将
await
用到非async
的函数中。当你将
await
添加到一个函数上的时候:首先,它会让这个函数本身返回一个 Promise(或者将返回的内容包装到 Promise 中);其次,它会确保你能够在这个函数中使用await
。
Node.js 异步最佳实践 & 避免回调地狱 | @RisingStack
要成为一个高效的 Node.js 开发者,你必须避免不断增加的缩进级别,书写简洁且易读的代码,以及处理复杂的流程。
尽可能使用异步 API 而不是同步 API。因为非阻塞的方式比同步的方式性能更好。
InfoQ:JavaScript 异步编程的 Promise 模式
关键字:2011 年 9 月 15 日、JavaScript、Ajax 示例。
Promise
- Node.js 最新技术栈之 Promise 篇 ⭐️⭐️⭐️
- 理解 Promise 的工作原理 ⭐️
- Promise 迷你书 ⭐️
- promise模块:bluebird
- promise模块: q
// 回调地狱
asyncOperation(function(data1) {
// 处理 data1
anotherAsync(function(data2) {
// 处理 data2
yetAnotherAsync(function() {
// 完成
});
});
});
// Promise 语法
promiseSomething()
.then(function(data1) {
// 处理 data1
return anotherAsync();
})
.then(function(data2) {
// 处理 data2
return yetAnotherAsync();
})
.then(function() {
// 完成
});
Promise/A 规范
promise 表示一个最终值,该值由一个操作完成时返回。
- promise 有3种状态:未完成(Pending)、完成(Fulfilled)、失败(Rejected)。
- promise 的状态转换:
- 未完成 ➡️ 完成。
- 未完成 ➡️ 失败。
- promise 的状态转换只发生一次。
promise 的 then()
方法可以接受3个函数作为参数:
promiseSomething().then(function(fulfilled) {
// 当 promise 状态变成 fulfilled 时,调用此函数。
}, function(rejected) {
// 当 promise 状态变成 rejected 时,调用此函数
}, function(progress) {
//当返回进度信息时,调用此函数(可选)
});
Async
- async
- Document: async
async 函数的含义和用法 @阮一峰
const asyncMethod = async () => { // async 用于声明一个函数是异步的。
const f1 = await firstMethod(); // await 用于等待一个异步方法执行完成。
const f2 = await anotherMethod();
console.log(f1, f2);
}
async 函数就是 Generator 函数的语法糖。
async 函数就是将 Generator 函数的星号(
*
)替换成 async,将 yield 替换成 await,仅此而已。await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。
async 函数返回的是一个 Promise 对象。
await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。
理解 JavaScript 的 async/await ⭐️⭐️⭐️
async function testAsync() {
return 'Hello World';
}
const result = testAsync();
console.log(result); // Promise { 'Hello World' }
async 函数(包含函数语句、函数表达式、Lambda 表达式)会返回一个 Promise 对象,如果在函数中
return
一个直接量,async 会把这个直接量通过Promise.resolve()
封装成 Promise 对象。
async function testAsync() {
return 'Hello World';
}
testAsync().then(result => {
console.log(result); // Hello World
});
用 setTimeout
模拟耗时操作的示例
Promise 语法:
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => {
resolve('long_time_value');
}, 1000);
});
}
takeLongTime().then(result => {
console.log(result); // long_time_value
});
async/await 语法:
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => {
resolve('long_time_value');
}, 1000);
});
}
async function test() {
const result = await takeLongTime();
console.log(result);
}
test();
async/await 的优势在于处理 then 链。
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout
来模拟异步操作:
// 返回值:time+200
function takeLongTime(time) {
return new Promise(resolve => {
setTimeout(() => resolve(time + 200), time);
});
}
function step1(time) {
console.log(`step1 with ${time}`);
return takeLongTime(time);
}
function step2(time) {
console.log(`step2 with ${time}`);
return takeLongTime(time);
}
function step3(time) {
console.log(`step3 with ${time}`);
return takeLongTime(time);
}
promise 语法:
function promiseMethod() {
const time = 300;
step1(time)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
})
.catch(err => {
// 任何一个步骤有错误,程序跳转到这里执行。
console.log(err);
});
}
promiseMethod();
async/await 语法:
async function asyncMethod() {
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
}
asyncMethod();