模拟 iterator 接口:
let obj = {0:1,1:2,2:3,length:3,[Symbol.iterator]:function(){let index = 0;let that = thisreturn {next(){return {value:that[index],done:index++ >= that.length}}}}}let arr = [...obj]console.log(arr)
使用 generator:
function *gen(val){let a = yield val + 1let b = yield a + 2return a + b}function* gen(val) {let a = yield val + 1console.log('a', a)let b = yield a + 2console.log('b', b)return a + b}let it = gen(3) // 返回的是一个 迭代器 对象console.log(it.next(4)) // 第一次传参没用的console.log(it.next(5)) // 这个值会给 aconsole.log(it.next(6)) // 这个值会给 bconsole.log(it.next(7)) // 已经执行完毕了,传参没意思了// 输出// { value: 4, done: false }// a 5// { value: 7, done: false }// b 6// { value: 11, done: true }// { value: undefined, done: true }
使用 babel 转义上面的 generator 函数:
"use strict";var _marked = /*#__PURE__*/regeneratorRuntime.mark(gen);function gen(val) {var a, b;return regeneratorRuntime.wrap(function gen$(_context) {while (1) {switch (_context.prev = _context.next) {case 0:_context.next = 2;return val + 1;case 2:a = _context.sent;_context.next = 5;return a + 2;case 5:b = _context.sent;return _context.abrupt("return", a + b);case 7:case "end":return _context.stop();}}}, _marked, this);}
看上面编译后的代码发现,第一次执行 gen(),返回的是 regeneratorRuntime.wrap (里面包裹的一个函数),这个函数里面其实就是一个 while 循环,相当于把我们刚开始写的 gen 函数拆成了若干个多部分,每次我们调用 next 方法,指针就往下移动一个位置,并且 return 回去。直到执行完毕。
generator 原理:
function* test() {let a = 1 + 2;yield 2;yield 3;}
手写 generator 实现:
// cb 也就是编译过的 test 函数function generator(cb) {return (function() {var object = {next: 0,stop: function() {}};return {next: function() {var ret = cb(object);if (ret === undefined) return { value: undefined, done: true };return {value: ret,done: false};}};})();}// 如果你使用 babel 编译后可以发现 test 函数变成了这样function test() {var a;return generator(function(_context) {while (1) {switch ((_context.prev = _context.next)) {// 可以发现通过 yield 将代码分割成几块// 每次执行 next 函数就执行一块代码// 并且表明下次需要执行哪块代码case 0:a = 1 + 2;_context.next = 4;return 2;case 4:_context.next = 6;return 3;// 执行完毕case 6:case "end":return _context.stop();}}});}let it = test()it.next()
async await 其实就是 generator 的语法糖,就是带了自动执行器的 generator 函数。使用 babel 编译后,会发现,会先把 async 函数转成 generator 函数(_asyncToGenerator)。
_asyncToGenerator 函数是这样的,其实就是每次调用 next 方法后返回一个 promise ,并且拿到上一个 next 执行后的 value 值:
function _asyncToGenerator(fn) {return function() {var gen = fn.apply(this, arguments) // 生成迭代器对象 let it = gen()return new Promise(function(resolve, reject) {function step(key, arg) {try {var info = gen[key](arg) // 调用迭代器的 next 方法 it.next()var value = info.value} catch (error) {reject(error)return}if (info.done) {// generator 执行完成后直接调用 async 函数返回的 Promise 的 resolve 方法resolve(value)} else {// 每个 await 后面返回的都是一个 Promise,并且把上一次 it.next 的 value 值给下一个 yield 的返回值,也就是给了 awaitreturn Promise.resolve(value).then(function(value) {step('next', value)},function(err) {step('throw', err)})}}return step('next')})}}
generator/iterator 并非异步代码,只是在缺少 async/await 的时候,一些框架(最著名的要数 co)使用这
样的特性来模拟 async/await。
generator+promise 异步:
let { promisify } = require('bluebird')let fs = require('fs')let read = promisify(fs.readFile)function * gen() {let a = yield read('1.txt', 'utf-8')let b = yield read('2.txt', 'utf-8')console.log(a)console.log(b)}let it = gen()//手动执行:// it.next().value.then(data => {// // console.log(data)// it.next(data).value.then(da => {// // console.log(da)// it.next(da)// })// })//模拟co库:function co(it){return new Promise((resolve,reject)=>{function next(data){let {value,done}= it.next(data)if(!done){value.then(re=>{next(re)},reject)}else{resolve(data)}}next()})}co(it)
generator的自动执行也可用采用thunk函数来处理。只不过yield后面就不能是返回一个promise,而是一个thunk函数。
参考:http://es6.ruanyifeng.com/#docs/generator-async
使用async-await:
async function readAsync(){let a = await read('1.txt', 'utf-8')let b = await read('2.txt', 'utf-8')console.log(a,b)}readAsync()
async 函数总是返回一个promise,解决await串行问题,可使用。
Promise.all([read('1.txt', 'utf-8'),read('2.txt', 'utf-8')])
处理异常可用用try catch包裹await。或者在await后面的promise直接处理。
async await继发、并发问题:
async await并发问题:
function sleep(i) {return new Promise((resolve, reject) => {setTimeout(function () {resolve(i);}, 1000);});}//如果直接这样使用,需要5秒才能全部输出完毕async function start(finsh_callback) {console.time('g');for (let i = 0; i < 5; i++) {let res = await sleep(i);console.log(res);}console.timeEnd('g');}//如果每次都是一个新的async await自执行则是并行的。当前也可以使用Promise.all()for (let i = 0; i < 5; i++) {(async () => {let res = await sleep(i);console.log(res);})()}
function wait(ms) {return new Promise(r => setTimeout(r, ms))}//以下代码执行完毕需要 1000 毫秒,再看看这段代码:async function series() {await wait(500)await wait(500)return 'done!'}//只需 500 毫秒就可执行完毕,因为两个 wait 是同时发生的:async function parallel() {const wait1 = wait(500)const wait2 = wait(500)await wait1await wait2return 'done!'}
继发执行:
async function dbFuc(db) {let docs = [{}, {}, {}];for (let doc of docs) {await db.post(doc);}}
并发执行:
async function dbFuc(db) {let docs = [{}, {}, {}];let promises = docs.map((doc) => db.post(doc));let results = await Promise.all(promises);console.log(results);}// 或者使用下面的写法async function dbFuc(db) {let docs = [{}, {}, {}];let promises = docs.map((doc) => db.post(doc));let results = [];for (let promise of promises) {results.push(await promise);}console.log(results);}
依次远程读取一组 URL,然后按照读取的顺序输出结果:
Promise 的写法如下:
function logInOrder(urls) {// 远程读取所有URLconst textPromises = urls.map(url => {return fetch(url).then(response => response.text());});// 按次序输出textPromises.reduce((chain, textPromise) => {return chain.then(() => textPromise).then(text => console.log(text));}, Promise.resolve());}
上面代码使用fetch方法,同时远程读取一组 URL。每个fetch操作都返回一个 Promise 对象,放入textPromises数组。然后,reduce方法依次处理每个 Promise 对象,然后使用then,将所有 Promise 对象连起来,因此就可以依次输出结果。
这种写法不太直观,可读性比较差。下面是 async 函数实现。
async function logInOrder(urls) {for (const url of urls) {const response = await fetch(url);console.log(await response.text());}}
如果确实需要并发发出远程请求。
async function logInOrder(urls) {// 并发读取远程URLconst textPromises = urls.map(async url => {const response = await fetch(url);return response.text();});// 按次序输出for (const textPromise of textPromises) {console.log(await textPromise);}}
async await 错误处理:
(async function (){let [err, data] = await fetchData().then(data=>[null,data]).catch(err=>[err,null])})()
