Generator
生成器是一种返回迭代器对象的函数。
形式上,Generator 函数是一个普通函数,但是有两个特征
- function关键字与函数名之间有一个星号;
- 函数体内部使用yield表达式,定义不同的内部状态
- yield 表达式后面的表达式,只有当调用 next 方法、内部指针指向该语句时才会执行
- yield 关键字实际返回一个 Iterator 对象,它有两个属性,
value
和done
- yield 表达式 用于委托给另一个 generator 或可迭代对象。 ```javascript 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 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的 Symbol.iterator 属性,从而使得该对象具有 Iterator 接口。
```javascript
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
Generator 函数执行后,返回一个遍历器对象。该对象本身也具有 Symbol.iterator 属性,执行后返回自身。
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
for…of 循环可以自动遍历 Generator 函数运行时生成的 Iterator对象,且此时不再需要调用 next方法。
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 函数将异步操作表示得很简洁,但是流程管理却不方便
Thunk函数
Thunk 函数是自动执行 Generator 函数的一种方法。
const Thunk = function(fn) {
return function (...args) {
return function (callback) {
return fn.call(this, ...args, callback);
}
};
};
// 生成fs.readFile的 Thunk 函数
var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);
基于 Thunk 函数的 Generator 执行器
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if (result.done) return;
result.value(next);
}
next();
}
// 跟在yield命令后面的必须是 Thunk 函数
function* g() {
var f1 = yield readFileThunk('fileA');
var f2 = yield readFileThunk('fileB');
// ...
var fn = yield readFileThunk('fileN');
}
run(g);
co 模块
自执行器
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
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。
async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。
async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到。
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log('resolve', v),
e => console.log('reject', e)
)
//reject Error: 出错了
async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误。也就是说,只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数。
async 函数的实现原理
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
其中的 spawn 函数就是自动执行器,spawn 函数的实现,基本就是前文自动执行器的翻版。
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
await
await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,则会执行Promise.resolve()进行转换。
awai t命令后面的 Promise对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
多个 await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
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;
forEach问题
function dbFuc(db) { //这里不需要 async
let docs = [{}, {}, {}];
// 可能得到错误结果
docs.forEach(async function (doc) {
await db.post(doc);
});
}
// 原理
Array.prototype.forEach = function (callback) {
for (let index = 0; index < this.length; index++) {
callback(this[index], index, this);
}
};
// callback前面并没有使用await,所以是同步执行的
正确的写法是采用 for..of 循环
async function dbFuc(db) {
let docs = [{}, {}, {}];
//继发执行
for (let doc of docs) {
await db.post(doc);
}
如果确实希望多个请求并发执行,可以使用 Promise.all 方法。当三个请求都会 resolved 时返回
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}