学习链接
异步操作-Generator
Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆 then
,原来的语义变得很不清楚。
异步编程(多任务)的一种解决方案——“协程”(coroutine),意思是多个线程互相协作,完成异步任务。
协程有点像函数,又有点像线程。它的运行流程大致如下。
- 第一步,协程
A
开始执行。 - 第二步,协程
A
执行到一半,进入暂停,执行权转移到协程B
。 - 第三步,(一段时间后)协程
B
交还执行权。 - 第四步,协程
A
恢复执行。
上面流程的协程 A
,就是异步任务,因为它分成两段(或多段)执行。
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。
Thunk 函数
“求值策略”,即函数的参数到底应该何时求值。
var x = 1;
function f(m) {
return m * 2;
}
f(x + 5)
- 传值调用:即在进入函数体之前,就计算
x + 5
的值(等于 6),再将这个值传入函数f
。f(x + 5) => f(6)
- 传名调用:即直接将表达式
x + 5
传入函数体,只在用到它的时候求值。f(x + 5) => (x + 5) * 2
编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
Thunk 函数是“传名调用”的一种实现策略,用来替换某个表达式。
可用于自动执行 Generator 函数。
function thunkify(fn) {
return function (...args) {
const ctx = this;
return function (done) {
let called;
args.push(function () {
if (called) return;
called = true;
done.apply(null, arguments);
});
try {
fn.apply(ctx, args);
} catch (err) {
done(err);
}
}
}
};
手动流程管理
const readFileThunk = thunkify(fs.readFile);
const gen = function* (){
var r1 = yield readFileThunk('fileA');
console.log(r1.toString());
var r2 = yield readFileThunk('fileB');
console.log(r2.toString());
};
const g = gen();
let r1 = g.next();
r1.value(function (err, data) {
if (err) throw err;
let r2 = g.next(data);
r2.value(function (err, data) {
if (err) throw err;
g.next(data);
});
});
自动流程管理
const readFileThunk = thunkify(fs.readFile);
function run(fn) {
const gen = fn();
function next(err, data) {
const result = gen.next(data);
if (result.done) return;
result.value(next);
}
next();
}
function* g() {
let f1 = yield readFileThunk('fileA');
let f2 = yield readFileThunk('fileB');
// ...
let fn = yield readFileThunk('fileN');
}
run(g);
Thunk 函数并不是 Generator 函数自动执行的唯一方案。
因为自动执行的关键是,必须有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点。
co 模块
基于 Thunk 和 Promise。
// 接受 Generator 函数作为参数,返回 Promise 对象
function co(gen) {
const ctx = this;
return new Promise(function(resolve, reject) {
// 先检查参数gen是否为 Generator 函数
// 如果是,则执行该函数 得到一内部指针对象
if (typeof gen === 'function') gen = gen.call(ctx);
// 如果不是,则返回 Promised 对象
if (!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled();
// 将 Generator 函数的内部指针对象的 next 方法包装成 onFulfilled 函数
function onFulfilled(res) {
let ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
});
}
function next(ret) {
// 当前是否为 Generator 函数的最后一步
if (ret.done) {
return resolve(ret.value);
}
// 确保每一步的返回值都是 Promise 对象
let value = toPromise.call(ctx, ret.value);
// 使用then方法,为返回值加上回调函数,然后通过onFulfilled函数再次调用next函数
if (value && isPromise(value)) {
return value.then(onFulfilled, onRejected);
}
// 在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象)
// 将 Promise 对象的状态改为rejected,从而终止执行
return onRejected(
new TypeError(
'You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "'
+ String(ret.value)
+ '"'
)
);
}
Generator 与协程
Generator 与上下文
语法
语法简介
// 惰性求值
function* gen() {
yield 123 + 456;
}
// 暂缓执行
function* f() {
console.log('执行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
a.forEach(function (item) {
if (typeof item !== 'number') {
yield* flat(item); // Uncaught SyntaxError: Unexpected identifier
} else {
yield item; // Uncaught SyntaxError: Unexpected identifier
}
});
};
for (var f of flat(arr)){
console.log(f);
}
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
function* gen(x){
var y = yield x + 2;
// 等价于 yield (x + 2)
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
next 方法的参数
// 注意:执行了 yield 表达式后就暂停了, 表达式并未返回值
// yield 表达式的返回值由下一次的 next 的参数决定
// 故第二次调用 next 才输出 1.a
function* dataConsumer() {
console.log('Started');
console.log('1. ' + yield);
console.log('2. ' + yield);
return 'result';
}
let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')
// 2. b
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
// 不修改原生对象,捏造一个新接口
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj);
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
// 直接给对象 Symbol.iterator 赋值
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
function* numbers () {
yield 1;
yield 2;
return 3;
yield 4;
}
// 扩展运算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解构赋值
let [x, y] = numbers();
x // 1
y // 2
// for...of 循环
for (let n of numbers()) {
console.log(n)
}
// 1
// 2
Generator.prototype.throw()
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b'); // 内部 catch 过了,传到外部
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
var gen = function* gen(){
yield console.log('hello');
yield console.log('world');
}
var g = gen();
g.next();
try {
throw new Error();
} catch (e) {
g.next();
}
// hello
// world
function* gen() {
try {
yield 1;
} catch (e) {
console.log('内部捕获');
}
}
var g = gen();
g.throw(1);
// Uncaught 1
var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c
Generator.prototype.return()
function* gen() {
yield 1;
console.log(123)
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 }
g.return('foo 123') // { value: "foo 123", done: true }
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.return(8) // { value: 8, done: true }
g.next() // { value: undefined, done: true }
g.next() // { value: undefined, done: true }
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 }
next()、throw()、return() 的共同点
const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
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"
function* foo() {
yield 2;
yield 3;
return "foo";
}
function* bar() {
yield 1;
var v = yield* foo();
console.log("v: " + v);
yield 4;
}
var it = bar();
it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}
function* iterTree(tree) {
if (Array.isArray(tree)) {
for(let i = 0; i < tree.length; i++) {
yield* iterTree(tree[i]);
}
} else {
yield tree;
}
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
[...iterTree(tree)] // ["a", "b", "c", "d", "e"]
for(let x of iterTree(tree)) {
console.log(x);
}
// a
// b
// c
// d
// e
Generator 函数的 this
function* g() {}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi!'
function* g() {
this.a = 11;
}
let obj = g();
obj.next();
obj.a // undefined
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var obj = {};
var f = F.call(obj); // 即让 this 指向 obj
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
obj.a // 1
obj.b // 2
obj.c // 3
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype); // this 指向原型
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3