介於上一篇 「今日頭條」前端面試題和思路解析 中提到的 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:歡迎關注個人公衆號 「超哥前端小棧」,交流更多的想法與技術。