Generator 函數是一個狀態機,封裝了多個內部狀態。執行 Generator 函數會返回一個遍歷器對象,能夠依次遍歷 Generator 函數內部的每個狀態。html
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; }; var hw = helloWorldGenerator(); hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
Generator函數調用後不會馬上執行,而是返回一個指向內部狀態的指針對象。
調用該對象的next()方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield語句(或return語句)爲止。
yield語句是暫停執行的標記,而next方法能夠恢復執行。
遍歷器對象的next方法的運行邏輯以下。node
(1)遇到yield語句,就暫停執行後面的操做,並將緊跟在yield後面的那個表達式的值,做爲返回的對象的value屬性值。git
(2)下一次調用next方法時,再繼續往下執行,直到遇到下一個yield語句。github
(3)若是沒有再遇到新的yield語句(最後一個yield返回的對象的done爲false),就一直運行到函數結束,直到return語句爲止,並將return語句後面的表達式的值,做爲返回的對象的value屬性值(對象的done爲true)。shell
(4)若是該函數沒有return語句,則返回的對象的value屬性值爲undefined。json
yield句自己沒有返回值,或者說老是返回undefined。next方法能夠帶一個參數,該參數就會被看成上一個yield語句的返回值。api
function* f() { for(var i = 0; true; i++) { var reset = yield i; if(reset) { i = -1; } } } var g = f(); g.next() // { value: 0, done: false } g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false }
function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }
for...of循環能夠自動遍歷Generator函數時生成的Iterator對象,且此時再也不須要調用next方法。
一旦next方法的返回對象的done屬性爲true,for...of循環就會停止,且不包含該返回對象,因此下面代碼的return語句返回的6,不包括在for...of循環之中。異步
function *foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5
Generator函數返回的遍歷器對象,還有一個return方法,能夠返回給定的值,而且終結遍歷Generator函數。async
function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next() // { value: 1, done: false } g.return('foo') // { value: "foo", done: true } g.next() // { value: undefined, done: true }
若是return方法調用時,不提供參數,則返回值的value屬性爲undefined。函數
function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next() // { value: 1, done: false } g.return() // { value: undefined, done: true }
若是Generator函數內部有try...finally代碼塊,那麼return方法會推遲到finally代碼塊執行完再執行。
function* numbers () { yield 1; try { yield 2; yield 3; } finally { yield 4; yield 5; } yield 6; } var g = numbers(); g.next() // { value: 1, done: false } g.next() // { value: 2, done: false } g.return(7) // { value: 4, done: false } g.next() // { value: 5, done: false } g.next() // { value: 7, done: true }
在一個 Generator 函數裏面執行另外一個 Generator 函數要用到yield*語句。
function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; yield* foo(); yield 'y'; } // 等同於 function* bar() { yield 'x'; yield 'a'; yield 'b'; yield 'y'; } // 等同於 function* bar() { yield 'x'; for (let v of foo()) { yield v; } yield 'y'; } for (let v of bar()){ console.log(v); } // "x" // "a" // "b" // "y"
異步任務的封裝
var fetch = require('node-fetch'); function* gen(){ var url = 'https://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio); } var g = gen(); var result = g.next(); result.value.then(function(data){ return data.json(); }).then(function(data){ g.next(data); });
Generator自動執行器
function run(fn) { var gen = fn(); function next(err, data) { var result = gen.next(data); if (result.done) return; result.value(next); } next(); } var g = function* (){ var f1 = yield readFile('fileA'); var f2 = yield readFile('fileB'); // ... var fn = yield readFile('fileN'); }; run(g);
基於 Promise 對象的自動執行
var fs = require('fs'); var readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) return reject(error); resolve(data); }); }); }; var gen = function* (){ var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }; function run(gen){ var g = gen(); function next(data){ var result = g.next(data); if (result.done) return result.value; result.value.then(function(data){ next(data); }); } next(); } run(gen);
async 函數,一句話,它就是 Generator 函數的語法糖。
上面的例子,寫成async函數,就是下面這樣:
var fs = require('fs'); var readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) return reject(error); resolve(data); }); }); }; var asyncReadFile = function (){ var f1 = await readFile('/etc/fstab'); var f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }; asyncReadFile();
async函數對 Generator 函數的改進,體如今如下四點。
(1)內置執行器。
(2)更好的語義。
(3)更廣的適用性。
(4)返回值是 Promise。
async函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。
async函數內部return語句返回的值,會成爲then方法回調函數的參數。
async function f() { return 'hello world'; } f().then(v => console.log(v))
async函數內部報錯錯誤會致使返回的Promise對象變爲reject狀態,拋出的錯誤會被catch方法接受到
async function f() { throw new Error('this is an error'); } f().then( v => console.log(v), e => console.log(e) )
async函數返回的 Promise 對象,必須等到內部全部await命令後面的 Promise 對象執行完,纔會發生狀態改變,除非遇到return語句或者拋出錯誤。也就是說,只有async函數內部的異步操做執行完,纔會執行then方法指定的回調函數。
async function getTitle(url) { let response = await fetch(url); let html = await response.text(); return html.match(/<title>([\s\S]+)<\/title>/i)[1]; } getTitle('https://tc39.github.io/ecma262/').then(console.log) // "ECMAScript 2017 Language Specification"
上面代碼中,函數getTitle內部有三個操做:抓取網頁、取出文本、匹配頁面標題。只有這三個操做所有完成,纔會執行then方法裏面的console.log。
正常狀況下,await命令後面是一個 Promise 對象。若是不是,會被轉成一個當即resolve的 Promise 對象。
async function f() { return await 123; } f().then(v => console.log(v)) // 123
上面代碼中,await命令的參數是數值123,它被轉成 Promise 對象,並當即resolve。
await命令後面的 Promise 對象若是變爲reject狀態,則reject的參數會被catch方法的回調函數接收到。
async function f() { await Promise.reject('出錯了'); } f() .then(v => console.log(v)) .catch(e => console.log(e)) // 出錯了
只要一個await語句後面的 Promise 變爲reject,那麼整個async函數都會中斷執行。
async function f() { await Promise.reject('出錯了'); await Promise.resolve('hello world'); // 不會執行 }
有時,咱們但願即便前一個異步操做失敗,也不要中斷後面的異步操做。這時能夠將第一個await放在try...catch結構裏面,這樣無論這個異步操做是否成功,第二個await都會執行。
async function f() { try { await Promise.reject('出錯了'); } catch(e) { } return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // hello world
另外一種方法是await後面的 Promise 對象再跟一個catch方法,處理前面可能出現的錯誤。
async function f() { await Promise.reject('出錯了') .catch(e => console.log(e)); return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // 出錯了 // hello world
若是有多個await命令,能夠統一放在try...catch結構中。
async function main() { try { var val1 = await firstStep(); var val2 = await secondStep(val1); var val3 = await thirdStep(val1, val2); console.log('Final: ', val3); } catch (err) { console.error(err); } }
多個await命令後面的異步操做,若是不存在繼發關係,最好讓它們同時觸發。
//getFoo和getBar是兩個獨立的異步操做(即互不依賴),被寫成繼發關係。這樣比較耗時,由於只有getFoo完成之後,纔會執行getBar,徹底可讓它們同時觸發。 let foo = await getFoo(); let bar = await getBar(); // 寫法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 寫法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;