介于上一篇 「今日头条」前端面试题和思路解析 中提到的 async/await,让我想起了以前写过的一篇文章,在此作个分享。它细说了什么是async函数,以及其相较于 Promise 的优点。html
温故而知新,正文开始。前端
谈及异步回调函数的嵌套,总会让人感到烦躁,特别是当业务逻辑复杂,每每须要调用几回 ajax 才能拿到全部须要的数据。面试
从最先的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人以为不完全。它们都有额外的复杂性,都须要理解抽象的底层运行机制。因此,咱们须要一种方法,更优雅地解决异步操做。因而,async函数出现了。ajax
一句话解释:async 函数,就是 Generator 函数的语法糖。编程
它有如下几个特色:segmentfault
async 函数的实现,其实就是将 Generator 函数和自动执行器,包装在一个函数里。下面的这个例子,来自阮老师的 《async 函数的含义和用法》 一文。promise
async function fn(args) { // ... } // 等同于 function fn(args) { return spawn(function*() { // ... }); } // spawn 函数就是自动执行器 function spawn(genF) { return new Promise(function(resolve, reject) { var gen = genF(); function step(nextF) { try { var next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }
因此说,async 函数是 Generator 函数的语法糖。另外,它相对较新,属于ES7中的语法。可是转码器 Babel 已经支持,转码后就能使用。异步
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 方式来实现这三个步骤的处理。async
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}`); }); } doIt(); // step1 with 300 // step2 with 500 // step3 with 700 // result is 900
若是用 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}`); } doIt();
结果和以前的 Promise 实现是同样的,可是这个代码看起来是否是清晰得多,几乎跟同步代码同样。
如今把业务要求改一下,仍然是三个步骤,但每个步骤都须要以前每一个步骤的结果。Pomise的实现看着很晕,传递参数太过麻烦。
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}`); }); } doIt();
用 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}`); } doIt();
没有多余的中间值,更加优雅地实现了。
相比于 Promise 更易于调试。
由于没有代码块,因此不能在一个返回的箭头函数中设置断点。若是你在一个 .then 代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的 .then 代码块,由于调试器只能跟踪同步代码的每一步。
如今,若是使用 async/await,你就没必要再使用箭头函数。你能够对 await 语句执行步进操做,就好像他们都是普通的同步语句同样。
JavaScript的异步编写方式,从 回调函数 到 Promise、Generator 再到 Async/Await。表面上只是写法的变化,但本质上则是语言层的一次次抽象。让咱们能够用更简单的方式实现一样的功能,而不须要去考虑代码是如何执行的。
换句话说就是:异步编程的最高境界,就是根本不用关心它是否是异步。
PS:欢迎关注个人公众号 “超哥前端小栈”,交流更多的想法与技术。