Generator

生成器是一种返回迭代器对象的函数。

形式上,Generator 函数是一个普通函数,但是有两个特征

  • function关键字与函数名之间有一个星号;
  • 函数体内部使用yield表达式,定义不同的内部状态
  • yield 表达式后面的表达式,只有当调用 next 方法、内部指针指向该语句时才会执行
  • yield 关键字实际返回一个 Iterator 对象,它有两个属性,valuedone
  • 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 }

  1. 由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的 Symbol.iterator 属性,从而使得该对象具有 Iterator 接口。
  2. ```javascript
  3. var myIterable = {};
  4. myIterable[Symbol.iterator] = function* () {
  5. yield 1;
  6. yield 2;
  7. yield 3;
  8. };
  9. [...myIterable] // [1, 2, 3]

Generator 函数执行后,返回一个遍历器对象。该对象本身也具有 Symbol.iterator 属性,执行后返回自身。

  1. function* gen(){
  2. // some code
  3. }
  4. var g = gen();
  5. g[Symbol.iterator]() === g
  6. // true

for…of 循环可以自动遍历 Generator 函数运行时生成的 Iterator对象,且此时不再需要调用 next方法。

  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); // 1 2 3 4 5
  11. }

虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便

Thunk函数

Thunk 函数是自动执行 Generator 函数的一种方法。

  1. const Thunk = function(fn) {
  2. return function (...args) {
  3. return function (callback) {
  4. return fn.call(this, ...args, callback);
  5. }
  6. };
  7. };
  1. // 生成fs.readFile的 Thunk 函数
  2. var readFileThunk = Thunk(fs.readFile);
  3. readFileThunk(fileA)(callback);

基于 Thunk 函数的 Generator 执行器

  1. function run(fn) {
  2. var gen = fn();
  3. function next(err, data) {
  4. var result = gen.next(data);
  5. if (result.done) return;
  6. result.value(next);
  7. }
  8. next();
  9. }
  10. // 跟在yield命令后面的必须是 Thunk 函数
  11. function* g() {
  12. var f1 = yield readFileThunk('fileA');
  13. var f2 = yield readFileThunk('fileB');
  14. // ...
  15. var fn = yield readFileThunk('fileN');
  16. }
  17. run(g);

co 模块

自执行器

  1. function run(gen){
  2. var g = gen();
  3. function next(data){
  4. var result = g.next(data);
  5. if (result.done) return result.value;
  6. result.value.then(function(data){
  7. next(data);
  8. });
  9. }
  10. next();
  11. }
  12. run(gen);

async

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。

async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。

async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到。

  1. async function f() {
  2. throw new Error('出错了');
  3. }
  4. f().then(
  5. v => console.log('resolve', v),
  6. e => console.log('reject', e)
  7. )
  8. //reject Error: 出错了

async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误。也就是说,只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数。

async 函数的实现原理

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

  1. async function fn(args) {
  2. // ...
  3. }
  4. // 等同于
  5. function fn(args) {
  6. return spawn(function* () {
  7. // ...
  8. });
  9. }

其中的 spawn 函数就是自动执行器,spawn 函数的实现,基本就是前文自动执行器的翻版。

  1. function spawn(genF) {
  2. return new Promise(function(resolve, reject) {
  3. const gen = genF();
  4. function step(nextF) {
  5. let next;
  6. try {
  7. next = nextF();
  8. } catch(e) {
  9. return reject(e);
  10. }
  11. if(next.done) {
  12. return resolve(next.value);
  13. }
  14. Promise.resolve(next.value).then(function(v) {
  15. step(function() { return gen.next(v); });
  16. }, function(e) {
  17. step(function() { return gen.throw(e); });
  18. });
  19. }
  20. step(function() { return gen.next(undefined); });
  21. });
  22. }

await

await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,则会执行Promise.resolve()进行转换。

awai t命令后面的 Promise对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中。

  1. async function myFunction() {
  2. try {
  3. await somethingThatReturnsAPromise();
  4. } catch (err) {
  5. console.log(err);
  6. }
  7. }
  8. // 另一种写法
  9. async function myFunction() {
  10. await somethingThatReturnsAPromise()
  11. .catch(function (err) {
  12. console.log(err);
  13. });
  14. }

多个 await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

  1. let foo = await getFoo();
  2. let bar = await getBar();
  3. // 写法一
  4. let [foo, bar] = await Promise.all([getFoo(), getBar()]);
  5. // 写法二
  6. let fooPromise = getFoo();
  7. let barPromise = getBar();
  8. // 先执行后异步看结果
  9. let foo = await fooPromise;
  10. let bar = await barPromise;

forEach问题

  1. function dbFuc(db) { //这里不需要 async
  2. let docs = [{}, {}, {}];
  3. // 可能得到错误结果
  4. docs.forEach(async function (doc) {
  5. await db.post(doc);
  6. });
  7. }
  1. // 原理
  2. Array.prototype.forEach = function (callback) {
  3. for (let index = 0; index < this.length; index++) {
  4. callback(this[index], index, this);
  5. }
  6. };
  7. // callback前面并没有使用await,所以是同步执行的

正确的写法是采用 for..of 循环

  1. async function dbFuc(db) {
  2. let docs = [{}, {}, {}];
  3. //继发执行
  4. for (let doc of docs) {
  5. await db.post(doc);
  6. }

如果确实希望多个请求并发执行,可以使用 Promise.all 方法。当三个请求都会 resolved 时返回

  1. async function dbFuc(db) {
  2. let docs = [{}, {}, {}];
  3. let promises = docs.map((doc) => db.post(doc));
  4. let results = await Promise.all(promises);
  5. console.log(results);
  6. }