理解 JavaScript 的 async/await
转载 https://segmentfault.com/a/1190000007535316 边城大大
之前一直搞不懂 async/await ,趁现在不忙就一起学习吧。
1.async 和 await 在干什么的?
任意一个名称都是有意义的,先从字面意思来理解。async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
另外还有一个很有意思的语法规定,await 只能出现在 async 函数中。然后细心的朋友会产生一个疑问,如果 await 只能出现在 async 函数中,那这个 async 函数应该怎么调用?
如果需要通过 await 来调用一个 async 函数,那这个调用的外面必须得再包一个 async 函数,然后……进入死循环,永无出头之日……
如果 async 函数不需要 await 来调用,那 async 到底起个啥作用?
1.1 async 起什么作用
这个问题的关键在于,async 函数是怎么处理它的返回值的!
我们当然希望它能直接通过 return 语句返回我们想要的值,但是如果真是这样,似乎就没 await 什么事了。所以,写段代码来试试,看它到底会返回什么:
async function testAsync() { return "hello async"; } const result = testAsync(); console.log(result); //Promise { 'hello async' }
看到输出发现 竟然返回的是 promise 对象
所以,async 函数返回的是一个promise对象。从文档中也可以得到这个信息。async函数(包含函数语句。函数表达式,lambda表达式)会返回一个promise对象,如果在函数中return一个直接量,async会把这个直接量通过Promise.resolve()封装成Promise对象。
补充一点:Promise.resolve(X)可以看作new Promise(resolve => resolve(x))的简写,可以用来快速封装字面量对象或者其他对象,
async 函数返回的是一个promise对象,所以在最外层不能用await获取返回值的情况下,我们当然应该用原来的方式:then()来链式处理这个Promise对象,如下
async function getSomething() { return "something"; } getSomething().then((res)=>{ console.log(res,11) })现在回过头来想下,如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。
联想一下 Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。
1.2 await 到底在等啥
一般来说,都认为await是在等待一个async函数完成。过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。 因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行
async function getSomething() { return "something"; } async function testAsync() { return Promise.resolve("hello async"); } async function test() { const v1 = await getSomething(); const v2 = await testAsync(); console.log(v1, v2); // something ,hello async } test()
1.3. await 等到了要等的,然后呢
await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?我不得不先说,await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
看到上面的阻塞一词,心慌了吧……放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。
2. async/await 帮我们干了啥 (对比)
function longTime() { return new Promise(resolve=>{ setTimeout(()=>{ resolve('我是小仙女') },2000) }) } longTime().then((res)=>{ console.log(res) // 我是小仙女 })
function longTime() { return new Promise(resolve=>{ setTimeout(()=>{ resolve('我是小仙女') },2000) }) } async function test() { let res = await longTime() console.log(res) // 我是小仙女 } test()
2.2. async/await 的优势在于处理 then 链
没有用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); } function doit() { const time = 300 step1(time).then((res)=>{ step2(res).then((res)=>{ step3(res).then((res)=>{ console.log(res) // 900 }) }) }) } // step1 with 300 step2 with 500 step3 with 700
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); } async function doit() { const time = 300 let a = await step1(time) let b = await step2(a) let c = await step3(b) console.log(c) }
·