我们都知道已经有了Promise的解决方案了,为什么还要ES7提出新的Async/Await标准呢?
答案其实也显而易见:Promise虽然跳出了异步嵌套的怪圈,用链式表达更加清晰,但是我们也发现如果有大量的异步请求的时候,流程复杂的情况下,会发现充满了屏幕的then,看起来非常吃力,而ES7的Async/Await的出现就是为了解决这种复杂的情况。
Promise
-
Promise 对象是由关键字 new 及其构造函数来创建的。
-
该构造函数会把一个叫做“处理器函数”(executor function)的函数作为它的参数。
-
这个“处理器函数”接受两个函数——resolve 和 reject ——作为其参数。
-
当异步任务顺利完成且返回结果值时,会调用 resolve 函数;
-
而当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject 函数
基于Promise的Async/await
Async
- 当调用一个 async 函数时,会返回一个 promise 对象
- 当这个异步函数返回一个值时,async 通过 Promise.resolve() 将这个值封装成 promise 对象
- 当这个异步函数没有返回值时,会返回 Promise.resolve(undefined)
- 当 async 函数抛出异常时,Promise 的 reject 方法也会传递这个异常值
- async 函数中可能会有 await 表达式,这会使 async 函数暂停执行,等待 Promise 的结果出来,然后恢复async函数的执行并返回解析值(resolved
- 注意:Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致
await
- await 操作符用于等待一个Promise 对象。它只能在异步函数 async function 中使用。
- 语法:[return_value] = await expression; expression 是一个 Promise 对象或者任何要等待的值。返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
- await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。 若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async function。
- 若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。
- 另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。
- 注意重点:很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码, 实际上await是一个让出线程的标志。await后面的函数会先执行一遍,然后就会跳出整个async函数来执行后面js栈的代码。
- 等本轮事件循环执行完了之后又会跳回到async函数中等待await后面表达式的返回值
- 如果返回值为非 promise 则继续执行 async 函数后面的代码,否则将返回的 promise 放入 promise 队列(Promise的Job Queue)
async/await 的优势在于处理大量异步请求
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
现在用 Promise 方式来实现这三个步骤的处理
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。
如果用 async/await 来实现呢,会是这样
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样
需求升级
现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(m, n) {
console.log(`step2 with ${m} and ${n}`);
return takeLongTime(m + n);
}
function step3(k, m, n) {
console.log(`step3 with ${k}, ${m} and ${n}`);
return takeLongTime(k + m + n);
}
这回先用 async/await 来写:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 Promise 方式实现会是什么样子?
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => {
return step2(time1, time2)
.then(time3 => [time1, time2, time3]);
})
.then(times => {
const [time1, time2, time3] = times;
return step3(time1, time2, time3);
})
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
有没有感觉有点复杂的样子?那一堆参数处理,就是 Promise 方案的死穴—— 参数传递太麻烦了,看着就晕!
所以说
async/await 是一种编写异步代码的新方法。之前异步代码的方案是回调和 promise。
async/await 是建立在 promise 的基础上。
async/await 像 promise 一样,也是非阻塞的。
async/await 让异步代码看起来、表现起来更像同步代码。这正是其威力所在