模拟 iterator 接口:
let obj = {0:1,1:2,2:3,length:3,[Symbol.iterator]:function(){
let index = 0;
let that = this
return {
next(){
return {
value:that[index],
done:index++ >= that.length
}
}
}
}}
let arr = [...obj]
console.log(arr)
使用 generator:
function *gen(val){
let a = yield val + 1
let b = yield a + 2
return a + b
}
function* gen(val) {
let a = yield val + 1
console.log('a', a)
let b = yield a + 2
console.log('b', b)
return a + b
}
let it = gen(3) // 返回的是一个 迭代器 对象
console.log(it.next(4)) // 第一次传参没用的
console.log(it.next(5)) // 这个值会给 a
console.log(it.next(6)) // 这个值会给 b
console.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 的返回值,也就是给了 await
return 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 wait1
await wait2
return '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) {
// 远程读取所有URL
const 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) {
// 并发读取远程URL
const 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])
})()