学习链接

Generator 函数的语法

Generator 函数的异步应用

进程、线程、协程、例程、过程的区别是什么?

异步操作-Generator

Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。

异步编程(多任务)的一种解决方案——“协程”(coroutine),意思是多个线程互相协作,完成异步任务。

协程有点像函数,又有点像线程。它的运行流程大致如下。

  • 第一步,协程 A 开始执行。
  • 第二步,协程 A 执行到一半,进入暂停,执行权转移到协程B
  • 第三步,(一段时间后)协程 B 交还执行权。
  • 第四步,协程 A 恢复执行。

上面流程的协程 A,就是异步任务,因为它分成两段(或多段)执行。

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。

虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。

Thunk 函数

“求值策略”,即函数的参数到底应该何时求值。

  1. var x = 1;
  2. function f(m) {
  3. return m * 2;
  4. }
  5. f(x + 5)
  • 传值调用:即在进入函数体之前,就计算 x + 5 的值(等于 6),再将这个值传入函数f
    1. f(x + 5) => f(6)
  • 传名调用:即直接将表达式 x + 5 传入函数体,只在用到它的时候求值。
    1. f(x + 5) => (x + 5) * 2

编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。

Thunk 函数是“传名调用”的一种实现策略,用来替换某个表达式。

可用于自动执行 Generator 函数。

  1. function thunkify(fn) {
  2. return function (...args) {
  3. const ctx = this;
  4. return function (done) {
  5. let called;
  6. args.push(function () {
  7. if (called) return;
  8. called = true;
  9. done.apply(null, arguments);
  10. });
  11. try {
  12. fn.apply(ctx, args);
  13. } catch (err) {
  14. done(err);
  15. }
  16. }
  17. }
  18. };

手动流程管理

  1. const readFileThunk = thunkify(fs.readFile);
  2. const gen = function* (){
  3. var r1 = yield readFileThunk('fileA');
  4. console.log(r1.toString());
  5. var r2 = yield readFileThunk('fileB');
  6. console.log(r2.toString());
  7. };
  8. const g = gen();
  9. let r1 = g.next();
  10. r1.value(function (err, data) {
  11. if (err) throw err;
  12. let r2 = g.next(data);
  13. r2.value(function (err, data) {
  14. if (err) throw err;
  15. g.next(data);
  16. });
  17. });

自动流程管理

  1. const readFileThunk = thunkify(fs.readFile);
  2. function run(fn) {
  3. const gen = fn();
  4. function next(err, data) {
  5. const result = gen.next(data);
  6. if (result.done) return;
  7. result.value(next);
  8. }
  9. next();
  10. }
  11. function* g() {
  12. let f1 = yield readFileThunk('fileA');
  13. let f2 = yield readFileThunk('fileB');
  14. // ...
  15. let fn = yield readFileThunk('fileN');
  16. }
  17. run(g);

Thunk 函数并不是 Generator 函数自动执行的唯一方案。

因为自动执行的关键是,必须有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点。

co 模块

基于 Thunk 和 Promise。

  1. // 接受 Generator 函数作为参数,返回 Promise 对象
  2. function co(gen) {
  3. const ctx = this;
  4. return new Promise(function(resolve, reject) {
  5. // 先检查参数gen是否为 Generator 函数
  6. // 如果是,则执行该函数 得到一内部指针对象
  7. if (typeof gen === 'function') gen = gen.call(ctx);
  8. // 如果不是,则返回 Promised 对象
  9. if (!gen || typeof gen.next !== 'function') return resolve(gen);
  10. onFulfilled();
  11. // 将 Generator 函数的内部指针对象的 next 方法包装成 onFulfilled 函数
  12. function onFulfilled(res) {
  13. let ret;
  14. try {
  15. ret = gen.next(res);
  16. } catch (e) {
  17. return reject(e);
  18. }
  19. next(ret);
  20. }
  21. });
  22. }
  23. function next(ret) {
  24. // 当前是否为 Generator 函数的最后一步
  25. if (ret.done) {
  26. return resolve(ret.value);
  27. }
  28. // 确保每一步的返回值都是 Promise 对象
  29. let value = toPromise.call(ctx, ret.value);
  30. // 使用then方法,为返回值加上回调函数,然后通过onFulfilled函数再次调用next函数
  31. if (value && isPromise(value)) {
  32. return value.then(onFulfilled, onRejected);
  33. }
  34. // 在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象)
  35. // 将 Promise 对象的状态改为rejected,从而终止执行
  36. return onRejected(
  37. new TypeError(
  38. 'You may only yield a function, promise, generator, array, or object, '
  39. + 'but the following object was passed: "'
  40. + String(ret.value)
  41. + '"'
  42. )
  43. );
  44. }

Generator 与协程

异步操作-Generator - 图1

Generator 与上下文

异步操作-Generator - 图2

语法

语法简介

异步操作-Generator - 图3

  1. // 惰性求值
  2. function* gen() {
  3. yield 123 + 456;
  4. }
  5. // 暂缓执行
  6. function* f() {
  7. console.log('执行了!')
  8. }
  9. var generator = f();
  10. setTimeout(function () {
  11. generator.next()
  12. }, 2000);
  1. var arr = [1, [[2, 3], 4], [5, 6]];
  2. var flat = function* (a) {
  3. a.forEach(function (item) {
  4. if (typeof item !== 'number') {
  5. yield* flat(item); // Uncaught SyntaxError: Unexpected identifier
  6. } else {
  7. yield item; // Uncaught SyntaxError: Unexpected identifier
  8. }
  9. });
  10. };
  11. for (var f of flat(arr)){
  12. console.log(f);
  13. }
  1. function* demo() {
  2. console.log('Hello' + yield); // SyntaxError
  3. console.log('Hello' + yield 123); // SyntaxError
  4. console.log('Hello' + (yield)); // OK
  5. console.log('Hello' + (yield 123)); // OK
  6. }
  1. function* demo() {
  2. foo(yield 'a', yield 'b'); // OK
  3. let input = yield; // OK
  4. }
  5. function* gen(x){
  6. var y = yield x + 2;
  7. // 等价于 yield (x + 2)
  8. return y;
  9. }
  10. var g = gen(1);
  11. g.next() // { value: 3, done: false }
  12. g.next(2) // { value: 2, done: true }
  1. function* gen(){
  2. // some code
  3. }
  4. var g = gen();
  5. g[Symbol.iterator]() === g
  6. // true

next 方法的参数

异步操作-Generator - 图4

  1. // 注意:执行了 yield 表达式后就暂停了, 表达式并未返回值
  2. // yield 表达式的返回值由下一次的 next 的参数决定
  3. // 故第二次调用 next 才输出 1.a
  4. function* dataConsumer() {
  5. console.log('Started');
  6. console.log('1. ' + yield);
  7. console.log('2. ' + yield);
  8. return 'result';
  9. }
  10. let genObj = dataConsumer();
  11. genObj.next();
  12. // Started
  13. genObj.next('a')
  14. // 1. a
  15. genObj.next('b')
  16. // 2. b

for…of 循环

异步操作-Generator - 图5

  1. function* foo() {
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. yield 4;
  6. yield 5;
  7. return 6;
  8. }
  9. for (let v of foo()) {
  10. console.log(v);
  11. }
  12. // 1 2 3 4 5
  1. // 不修改原生对象,捏造一个新接口
  2. function* objectEntries(obj) {
  3. let propKeys = Reflect.ownKeys(obj);
  4. for (let propKey of propKeys) {
  5. yield [propKey, obj[propKey]];
  6. }
  7. }
  8. let jane = { first: 'Jane', last: 'Doe' };
  9. for (let [key, value] of objectEntries(jane)) {
  10. console.log(`${key}: ${value}`);
  11. }
  12. // first: Jane
  13. // last: Doe
  1. // 直接给对象 Symbol.iterator 赋值
  2. function* objectEntries() {
  3. let propKeys = Object.keys(this);
  4. for (let propKey of propKeys) {
  5. yield [propKey, this[propKey]];
  6. }
  7. }
  8. let jane = { first: 'Jane', last: 'Doe' };
  9. jane[Symbol.iterator] = objectEntries;
  10. for (let [key, value] of jane) {
  11. console.log(`${key}: ${value}`);
  12. }
  13. // first: Jane
  14. // last: Doe
  1. function* numbers () {
  2. yield 1;
  3. yield 2;
  4. return 3;
  5. yield 4;
  6. }
  7. // 扩展运算符
  8. [...numbers()] // [1, 2]
  9. // Array.from 方法
  10. Array.from(numbers()) // [1, 2]
  11. // 解构赋值
  12. let [x, y] = numbers();
  13. x // 1
  14. y // 2
  15. // for...of 循环
  16. for (let n of numbers()) {
  17. console.log(n)
  18. }
  19. // 1
  20. // 2

Generator.prototype.throw()

异步操作-Generator - 图6

  1. var g = function* () {
  2. try {
  3. yield;
  4. } catch (e) {
  5. console.log('内部捕获', e);
  6. }
  7. };
  8. var i = g();
  9. i.next();
  10. try {
  11. i.throw('a');
  12. i.throw('b'); // 内部 catch 过了,传到外部
  13. } catch (e) {
  14. console.log('外部捕获', e);
  15. }
  16. // 内部捕获 a
  17. // 外部捕获 b
  1. var gen = function* gen(){
  2. yield console.log('hello');
  3. yield console.log('world');
  4. }
  5. var g = gen();
  6. g.next();
  7. try {
  8. throw new Error();
  9. } catch (e) {
  10. g.next();
  11. }
  12. // hello
  13. // 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()

异步操作-Generator - 图7

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() 的共同点

异步操作-Generator - 图8

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* 表达式

异步操作-Generator - 图9

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

异步操作-Generator - 图10

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