async / await是ES7的重要特性之一,也是目前社區裏公認的優秀異步解決方案。目前,async / await這個特性已是stage 3的建議,能夠看看TC39的進度,本篇文章將分享async / await是如何工做的,閱讀本文前,但願你具有Promise、generator、yield等ES6的相關知識。python
在詳細介紹async / await以前,先回顧下目前在ES6中比較好的異步處理辦法。下面的例子中數據請求用Node.js中的request模塊,數據接口採用Github v3 api文檔提供的repo代碼倉庫詳情API做爲例子演示。git
雖然Node.js的異步IO帶來了對高併發的良好支持,同時也讓「回調」成爲災難,很容易形成回調地獄。傳統的方式好比使用具名函數,雖然能夠減小嵌套的層數,讓代碼看起來比較清晰。可是會形成比較差的編碼和調試體驗,你須要常用用ctrl + f去尋找某個具名函數的定義,這使得IDE窗口常常上下來回跳動。使用Promise以後,能夠很好的減小嵌套的層數。另外Promise的實現採用了狀態機,在函數裏面能夠很好的經過resolve和reject進行流程控制,你能夠按照順序鏈式的去執行一系列代碼邏輯了。下面是使用Promise的一個例子:github
const request = require('request'); // 請求的url和header const options = { url: 'https://api.github.com/repos/cpselvis/zhihu-crawler', headers: { 'User-Agent': 'request' } }; // 獲取倉庫信息 const getRepoData = () => { return new Promise((resolve, reject) => { request(options, (err, res, body) => { if (err) { reject(err); } resolve(body); }); }); }; getRepoData() .then((result) => console.log(result);) .catch((reason) => console.error(reason);); // 此處若是是多個Promise順序執行的話,以下: // 每一個then裏面去執行下一個promise // getRepoData() // .then((value2) => {return promise2}) // .then((value3) => {return promise3}) // .then((x) => console.log(x))
不過Promise仍然存在缺陷,它只是減小了嵌套,並不能徹底消除嵌套。舉個例子,對於多個promise串行執行的狀況,第一個promise的邏輯執行完以後,咱們須要在它的then函數裏面去執行第二個promise,這個時候會產生一層嵌套。另外,採用Promise的代碼看起來依然是異步的,若是寫的代碼若是可以變成同步該多好啊!chrome
談到generator,你應該不會對它感到陌生。在Node.js中對於回調的處理,咱們常常用的TJ / Co就是使用generator結合promise來實現的,co是coroutine的簡稱,借鑑於python、lua等語言中的協程。它能夠將異步的代碼邏輯寫成同步的方式,這使得代碼的閱讀和組織變得更加清晰,也便於調試。npm
const co = require('co'); const request = require('request'); const options = { url: 'https://api.github.com/repos/cpselvis/zhihu-crawler', headers: { 'User-Agent': 'request' } }; // yield後面是一個生成器 generator const getRepoData = function* () { return new Promise((resolve, reject) => { request(options, (err, res, body) => { if (err) { reject(err); } resolve(body); }); }); }; co(function* () { const result = yield getRepoData; // ... 若是有多個異步流程,能夠放在這裏,好比 // const r1 = yield getR1; // const r2 = yield getR2; // const r3 = yield getR3; // 每一個yield至關於暫停,執行yield以後會等待它後面的generator返回值以後再執行後面其它的yield邏輯。 return result; }).then(function (value) { console.log(value); }, function (err) { console.error(err.stack); });
雖然co是社區裏面的優秀異步解決方案,可是並非語言標準,只是一個過渡方案。ES7語言層面提供async / await去解決語言層面的難題。目前async / await 在 IE edge中已經能夠直接使用了,可是chrome和Node.js尚未支持。幸運的是,babel已經支持async的transform了,因此咱們使用的時候引入babel就行。在開始以前咱們須要引入如下的package,preset-stage-3裏就有咱們須要的async/await的編譯文件。api
不管是在Browser仍是Node.js端都須要安裝下面的包。promise
$ npm install babel-core --save $ npm install babel-preset-es2015 --save $ npm install babel-preset-stage-3 --save
這裏推薦使用babel官方提供的require hook方法。就是經過require進來後,接下來的文件進行require的時候都會通過Babel的處理。由於咱們知道CommonJs是同步的模塊依賴,因此也是可行的方法。這個時候,須要編寫兩個文件,一個是啓動的js文件,另一個是真正執行程序的js文件。babel
啓動文件index.js併發
require('babel-core/register'); require('./async.js');
真正執行程序的async.js異步
const request = require('request'); const options = { url: 'https://api.github.com/repos/cpselvis/zhihu-crawler', headers: { 'User-Agent': 'request' } }; const getRepoData = () => { return new Promise((resolve, reject) => { request(options, (err, res, body) => { if (err) { reject(err); } resolve(body); }); }); }; async function asyncFun() { try { const value = await getRepoData(); // ... 和上面的yield相似,若是有多個異步流程,能夠放在這裏,好比 // const r1 = await getR1(); // const r2 = await getR2(); // const r3 = await getR3(); // 每一個await至關於暫停,執行await以後會等待它後面的函數(不是generator)返回值以後再執行後面其它的await邏輯。 return value; } catch (err) { console.log(err); } } asyncFun().then(x => console.log(`x: ${x}`)).catch(err => console.error(err));
注意點:
其實,async / await的用法和co差很少,await和yield都是表示暫停,外面包裹一層async 或者 co來表示裏面的代碼能夠採用同步的方式進行處理。不過async / await裏面的await後面跟着的函數不須要額外處理,co是須要將它寫成一個generator的。