Home > Article > Web Front-end > Understanding JavaScript’s async/await
With the release of Node 7, more and more people are beginning to study async/await, which is said to be the ultimate solution for asynchronous programming. The first time I saw this set of keywords was not in the JavaScript language, but in the syntax of C# 5.0. C#'s async/await needs to be used in .NET Framework 4.5 or above, so I was sad for a while - in order to be compatible with XP systems, the software we developed cannot use .NET Framework higher than 4.0.
Whether it is in C# or JavaScript, async/await are great features, and they are also very sweet syntactic sugar. C#'s async/await implementation is inseparable from the Task or Task24243fe46d427c058a18f95dab2c319e class, and JavaScript's async/await implementation is also inseparable from Promise.
Now put aside C# and .NET Framework and concentrate on studying JavaScript’s async/await.
What async and await are doing
Any name is meaningful, first understand it literally. async is the abbreviation of "asynchronous", and await can be thought of as the abbreviation of async wait. So it should be well understood that async is used to declare that a function is asynchronous, while await is used to wait for an asynchronous method to complete execution.
There is also an interesting grammatical rule, await can only appear in async functions. Then careful friends will have a question, if await can only appear in async functions, then how should this async function be called?
If you need to call an async function through await, then this call must be wrapped with another async function. function, and then...enters an infinite loop, never to make a breakthrough...
If the async function does not need await to call, then what role does async play?
What role does async play
The key to this question is , how does the async function handle its return value!
Of course we hope that it can return the value we want directly through the return statement, but if this is the case, it seems that there is nothing to do with await. So, write a piece of code and try it out to see what it will return:
async function testAsync() { return "hello async"; } const result = testAsync(); console.log(result);
When I saw the output, I suddenly realized that the output is a Promise object.
c:\var\test> node --harmony_async_await . Promise { 'hello async' }
So, the async function returns a Promise object. This information can also be obtained from the documentation. Async functions (including function statements, function expressions, and Lambda expressions) will return a Promise object. If a direct value is returned in the function, async will encapsulate the direct value into a Promise object through Promise.resolve().
The async function returns a Promise object, so when the outermost layer cannot use await to obtain its return value, of course we should use the original way: then() chain to handle this Promise object, like this
testAsync().then(v => { console.log(v); // 输出 hello async });
Now think back and think about it, what if the async function does not return a value? It is easy to think that it will return Promise.resolve(undefined).
Think of the characteristics of Promise - no waiting, so if you execute an async function without await, it will execute immediately, return a Promise object, and will never block subsequent statements. This is no different from a normal function that returns a Promise object.
Then the next key point is the await keyword.
await What are you waiting for? Generally speaking, it is believed that await is waiting for an async function to complete. However, according to the syntax, await is waiting for an expression, and the calculation result of this expression is a Promise object or other value (in other words, there are no special restrictions).
Because the async function returns a Promise object, await can be used to wait for the return value of an async function - it can also be said that await is waiting for the async function, but it must be clear that it is actually waiting for a return value. Note that await is not only used to wait for Promise objects, it can wait for the result of any expression. Therefore, await can actually be followed by ordinary function calls or direct quantities. So the following example can run correctlyfunction 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); } test();await waits until it gets what it wants to wait for, and then what happensawait waits for what it wants to wait for, a Promise object, or other value, and then what? I have to say first, await is a Operator, used to form expressions. The result of await expression depends on what it is waiting for. If what it is waiting for is not a Promise object, the result of the await expression is what it is waiting for. If it is waiting for a Promise object, await will be busy. It will block the following code, wait for the Promise object to resolve, and then get the resolved value as the result of the await expression. When you see the word blocking above, you feel panicked... Don't worry, this is why await must be used in async functions. Async function calls will not cause blocking. All blocking inside them are encapsulated in a Promise object and executed asynchronously.
What did async/await do for us
A simple comparison
It has been explained above that async will encapsulate the return value of the subsequent function (function expression or Lambda) into a Promise object, and await It will wait for this Promise to complete and return its resolved result.现在举例,用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写
function takeLongTime() { return new Promise(resolve => { setTimeout(() => resolve("long_time_value"), 1000); }); } takeLongTime().then(v => { console.log("got", v); });
如果改用 async/await 呢,会是这样
function takeLongTime() { return new Promise(resolve => { setTimeout(() => resolve("long_time_value"), 1000); }); } async function test() { const v = await takeLongTime(); console.log(v); } test();
眼尖的同学已经发现 takeLongTime() 没有申明为 async。实际上,takeLongTime() 本身就是返回的 Promise 对象,加不加 async结果都一样,如果没明白,请回过头再去看看上面的“async 起什么作用”。
又一个疑问产生了,这两段代码,两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显,甚至使用 async/await 还需要多写一些代码,那它的优势到底在哪?
async/await 的优势在于处理 then 链
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 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 有可能 reject 啊,怎么处理呢?如果需要并行处理3个步骤,再等待所有结果,又该怎么处理呢?