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;