Promise, Generator, async & await (转)

165 阅读38分钟

Promise, Generator, async & await

1.1 Promise 作用

Promise 大家应该都用过,ajax 库就是利用 Promise 封装的; 作用主要是解决地狱回调问题。

1.2 使用

1.2.1.方法一

new Promise((resolve, reject) => {
	resolve("这是第一个 resolve 值");
})
	.then((data) => {
		console.log(data); //会打印'这是第一个 resolve 值'
	})
	.catch(() => {});

new Promise((resolve, reject) => {
	reject("这是第一个 reject 值");
})
	.then((data) => {
		console.log(data);
	})
	.catch((data) => {
		console.log(data); //会打印'这是第一个 reject 值'
	});

1.2.2.方法二(静态方法)

Promise.resolve("这是第二个 resolve 值").then((data) => {
	console.log(data); // 会打印'这是第二个 resolve 值'
});

Promise.reject("这是第二个 reject 值")
	.then((data) => {
		console.log(data);
	})
	.catch((data) => {
		console.log(data); //这是第二个 reject 值
	});

1.2.3.方法三(多个 Promise 并行执行异步操作)

表示多个 Promise 都进入到 FulFilled 或者 Rejected 就会执行

const pOne = new Promise((resolve, reject) => {
	resolve(1);
});

const pTwo = new Promise((resolve, reject) => {
	resolve(2);
});

const pThree = new Promise((resolve, reject) => {
	resolve(3);
});

Promise.all([pOne, pTwo, pThree]).then(
	(data) => {
		console.log(data); // [1, 2, 3] 正常执行完毕会执行这个,结果顺序和promise实例数组顺序是一致的
	},
	(err) => {
		console.log(err); // 任意一个报错信息
	}
);

1.2.4.方法四(多个中一个正常执行)

表示多个 Promise 只有一个进入到 FulFilled 或者 Rejected 就会执行

const pOne = new Promise((resolve, reject) => {
	resolve(1);
});

const pTwo = new Promise((resolve, reject) => {
	resolve(2);
});

const pThree = new Promise((resolve, reject) => {
	// resolve(3);
});

Promise.race([pOne, pTwo, pThree]).then(
	(data) => {
		console.log(data); // 1 只要碰到FulFilled 或者 Rejected就会停止执行
	},
	(err) => {
		console.log(err); // 任意一个报错信息
	}
);

1.3 作用分析

1.3.1 参数和状态

  1. Promise 接受一个函数 handle 作为参数,handle 包括 resolve 和 reject 两个是函数的参数
  2. Promise 相当于一个状态机,有三种状态:pending,fulfilled,reject,初始状态为 pending
  3. 调用 resolve,状态由 pending => fulfilled
  4. 调用 reject,会由 pending => rejected
  5. 改变之后不会变化

1.3.2 then 方法

  1. 接受两个参数,onFulfilled 和 onRejected 可选的函数

  2. 不是函数必须被忽略

  3. onFullfilled: A. 当 promise 状态变为成功时必须被调用,其第一个参数为 promise 成功状态传入的值( resolve 执行时传入的值 ) B. 在 promise 状态改变前其不可被调用 C. 其调用次数不可超过一次

  4. onRejected:作用和 onFullfilled 类似,只不过是 promise 失败调用

  5. then 方法可以链式调用 A. 每次返回一个新的 Promise B. 执行规则和错误捕获:then 的返回值如果是非 Promise 直接作为下一个新 Promise 参数,如果是 Promise 会等 Promise 执行

    // 返回非 Promise
    let promise1 = new Promise((resolve, reject) => {
    	setTimeout(() => {
    		resolve();
    	}, 1000);
    });
    promise2 = promise1.then((res) => {
    	// 返回一个普通值
    	return "这里返回一个普通值";
    });
    promise2.then((res) => {
    	console.log(res); //1秒后打印出:这里返回一个普通值
    });
    
    // 返回 Promise
    let promise1 = new Promise((resolve, reject) => {
    	setTimeout(() => {
    		resolve();
    	}, 1000);
    });
    promise2 = promise1.then((res) => {
    	// 返回一个 Promise 对象
    	return new Promise((resolve, reject) => {
    		setTimeout(() => {
    			resolve("这里返回一个Promise");
    		}, 2000);
    	});
    });
    promise2.then((res) => {
    	console.log(res); //3秒后打印出:这里返回一个 Promise
    });
    

    C. onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须变为失败(Rejected),并返回失败的值 e

    let promise1 = new Promise((resolve, reject) => {
    	setTimeout(() => {
    		resolve("success");
    	}, 1000);
    });
    promise2 = promise1.then((res) => {
    	throw new Error("这里抛出一个异常e");
    });
    promise2.then(
    	(res) => {
    		console.log(res);
    	},
    	(err) => {
    		console.log(err); //1秒后打印出:这里抛出一个异常e
    	}
    );
    

    D. onFulfilled 不是函数且 promise1 状态为成功(Fulfilled),promise2 必须变为成功(Fulfilled)并返回 promise1 成功的值

    let promise1 = new Promise((resolve, reject) => {
    	setTimeout(() => {
    		resolve("success");
    	}, 1000);
    });
    promise2 = promise1.then("这里的onFulfilled本来是一个函数,但现在不是");
    promise2.then(
    	(res) => {
    		console.log(res); // 1秒后打印出:success
    	},
    	(err) => {
    		console.log(err);
    	}
    );
    

    E. onRejected 不是函数且 promise1 状态为失败(Rejected),promise2 必须变为失败(Rejected) 并返回 promise1 失败的值

    let promise1 = new Promise((resolve, reject) => {
    	setTimeout(() => {
    		reject("fail");
    	}, 1000);
    });
    promise2 = promise1.then((res) => res, "这里的onRejected本来是一个函数,但现在不是");
    promise2.then(
    	(res) => {
    		console.log(res);
    	},
    	(err) => {
    		console.log(err); // 1秒后打印出:fail
    	}
    );
    

    F. resolve 和 reject 结束一个 Promise 的调用 G. catch 方法,捕获异常

其实复杂的是在 then 的异常处理上,不过不用急,边看边理解

1.3.3 方法

  1. 静态 resolve 方法 参照 1.3.1 用法 2

  2. 静态 reject 方法 参照 1.3.1 用法 2

  3. 静态 all 方法 参照 1.3.1 用法 3

  4. 静态 race 方法 参照 1.3.1 用法 4

  5. 自定义 done 方法 Promise 对象的回调链,不管以 then 方法或 catch 方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise 内部的错误不会冒泡到全局) 因此,我们可以提供一个 done 方法,总是处于回调链的尾端,保证抛出任何可能出现的错误

    Promise.prototype.done = function (onFulfilled, onRejected) {
    	this.then(onFulfilled, onRejected).catch(function (reason) {
    		// 抛出一个全局错误
    		setTimeout(() => {
    			throw reason;
    		}, 0);
    	});
    };
    
  6. 自定义 finally 方法 finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作 它与 done 方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行

    Promise.prototype.finally = function (callback) {
    	let P = this.constructor;
    	return this.then(
    		(value) => P.resolve(callback()).then(() => value),
    		(reason) =>
    			P.resolve(callback()).then(() => {
    				throw reason;
    			})
    	);
    };
    

1.4 PolyFill

1.4.1 初级版

class MyPromise {
	constructor(handle) {
		// 判断handle函数与否
		if (typeof handle !== "function") {
			throw new Error("MyPromise must accept a function as a parameter");
		}

		// 添加状态
		this._status = "PENDING";
		// 添加状态
		this._value = undefined;

		// 执行handle
		try {
			handle(this._resolve.bind(this), this._reject.bind(this));
		} catch (err) {
			this._reject(err);
		}
	}

	// 添加 resovle 时执行的函数
	_resolve(val) {
		if (this._status !== "PENDING") return;
		this._status = "FULFILLED";
		this._value = val;
	}

	// 添加 reject 时执行的函数
	_reject(err) {
		if (this._status !== "PENDING") return;
		this._status = "REJECTED";
		this._value = err;
	}
}

回顾一下,初级版实现了 1,2,3 这三点功能,怎么样还是 so-easy 吧!

1.4.2 中级版

  1. 由于 then 方法支持多次调用,我们可以维护两个数组,将每次 then 方法注册时的回调函数添加到数组中,等待执行。 在初级的基础上加入成功回调函数队列和失败回调队列和 then 方法

    this._fulfilledQueues = [];
    this._rejectedQueues = [];
    
  2. then 方法

    then (onFulfilled, onRejected) {
      const { _value, _status } = this
      switch (_status) {
        // 当状态为pending时,将then方法回调函数加入执行队列等待执行
        case 'PENDING':
          this._fulfilledQueues.push(onFulfilled)
          this._rejectedQueues.push(onRejected)
          break
        // 当状态已经改变时,立即执行对应的回调函数
        case 'FULFILLED':
          onFulfilled(_value)
          break
        case 'REJECTED':
          onRejected(_value)
          break
      }
      // 返回一个新的Promise对象
      return new MyPromise((onFulfilledNext, onRejectedNext) => {
      })
    }
    
  3. then 方法规则完善

    // 添加then方法
    then (onFulfilled, onRejected) {
      const { _value, _status } = this
      // 返回一个新的Promise对象
      return new MyPromise((onFulfilledNext, onRejectedNext) => {
        // 封装一个成功时执行的函数
        let fulfilled = value => {
          try {
            if (typeof onFulfilled!=='function') {
              onFulfilledNext(value)
            } else {
              let res =  onFulfilled(value);
              if (res instanceof MyPromise) {
                // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                res.then(onFulfilledNext, onRejectedNext)
              } else {
                //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
                onFulfilledNext(res)
              }
            }
          } catch (err) {
            // 如果函数执行出错,新的Promise对象的状态为失败
            onRejectedNext(err)
          }
        }
        // 封装一个失败时执行的函数
        let rejected = error => {
          try {
            if (typeof onRejected!=='function') {
              onRejectedNext(error)
            } else {
                let res = onRejected(error);
                if (res instanceof MyPromise) {
                  // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                  res.then(onFulfilledNext, onRejectedNext)
                } else {
                  //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
                  onFulfilledNext(res)
                }
            }
          } catch (err) {
            // 如果函数执行出错,新的Promise对象的状态为失败
            onRejectedNext(err)
          }
        }
        switch (_status) {
          // 当状态为pending时,将then方法回调函数加入执行队列等待执行
          case 'PENDING':
            this._fulfilledQueues.push(fulfilled)
            this._rejectedQueues.push(rejected)
            break
          // 当状态已经改变时,立即执行对应的回调函数
          case 'FULFILLED':
            fulfilled(_value)
            break
          case 'REJECTED':
            rejected(_value)
            break
        }
      })
    }
    
  4. 当 resolve 或 reject 方法执行时,我们依次提取成功或失败任务队列当中的函数开始执行,并清空队列,从而实现 then 方法的多次调用

    // 添加resovle时执行的函数
    _resolve (val) {
      if (this._status !== PENDING) return
      // 依次执行成功队列中的函数,并清空队列
      const run = () => {
        this._status = FULFILLED
        this._value = val
        let cb;
        while (cb = this._fulfilledQueues.shift()) {
          cb(val)
        }
      }
      // 为了支持同步的Promise,这里采用异步调用
      setTimeout(() => run(), 0)
    }
    // 添加reject时执行的函数
    _reject (err) {
      if (this._status !== PENDING) return
      // 依次执行失败队列中的函数,并清空队列
      const run = () => {
        this._status = REJECTED
        this._value = err
        let cb;
        while (cb = this._rejectedQueues.shift()) {
          cb(err)
        }
      }
      // 为了支持同步的Promise,这里采用异步调用
      setTimeout(run, 0)
    }
    
  5. 当 resolve 方法传入的参数为一个 Promise 对象时,则该 Promise 对象状态决定当前 Promise 对象的状态

    // 添加resovle时执行的函数
    _resolve (val) {
      const run = () => {
        if (this._status !== PENDING) return
        this._status = FULFILLED
        // 依次执行成功队列中的函数,并清空队列
        const runFulfilled = (value) => {
          let cb;
          while (cb = this._fulfilledQueues.shift()) {
            cb(value)
          }
        }
        // 依次执行失败队列中的函数,并清空队列
        const runRejected = (error) => {
          let cb;
          while (cb = this._rejectedQueues.shift()) {
            cb(error)
          }
        }
        /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
          当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
        */
        if (val instanceof MyPromise) {
          val.then(value => {
            this._value = value
            runFulfilled(value)
          }, err => {
            this._value = err
            runRejected(err)
          })
        } else {
          this._value = val
          runFulfilled(val)
        }
      }
      // 为了支持同步的Promise,这里采用异步调用
      setTimeout(run, 0)
    }
    
  6. catch 方法

    // 添加catch方法
    catch (onRejected) {
      return this.then(undefined, onRejected)
    }
    

1.4.3 高级版

  1. 静态 resolve 方法

    // 添加静态resolve方法
    static resolve (value) {
      // 如果参数是MyPromise实例,直接返回这个实例
      if (value instanceof MyPromise) return value
      return new MyPromise(resolve => resolve(value))
    }
    
  2. 静态 reject 方法

    // 添加静态reject方法
    static reject (value) {
      return new MyPromise((resolve ,reject) => reject(value))
    }
    
  3. 静态 all 方法

    // 添加静态 all 方法
    static all (list) {
      return new MyPromise((resolve, reject) => {
        /**
        * 返回值的集合
        */
        let values = []
        let count = 0
        for (let [i, p] of list.entries()) {
          // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
          this.resolve(p).then(res => {
            values[i] = res
            count++
            // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
            if (count === list.length) resolve(values)
          }, err => {
            // 有一个被rejected时返回的MyPromise状态就变成rejected
            reject(err)
          })
        }
      })
    }
    
  4. 静态 race 方法

    // 添加静态 race 方法
    static race (list) {
      return new MyPromise((resolve, reject) => {
        for (let p of list) {
          // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
          this.resolve(p).then(res => {
            resolve(res)
          }, err => {
            reject(err)
          })
        }
      })
    }
    
  5. done 方法 作用:不管以 then 方法或 catch 方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise 内部的错误不会冒泡到全局);处于回调链的尾端,保证抛出任何可能出现的错误 目前 Promise 上还没有 done,我们可以自定义一个

    Promise.prototype.done = function (onFulfilled, onRejected) {
    	console.log("done");
    	this.then(onFulfilled, onRejected).catch((reason) => {
    		// 抛出一个全局错误
    		setTimeout(() => {
    			throw reason;
    		}, 0);
    	});
    };
    Promise.resolve("这是静态方法的第一个 resolve 值")
    	.then(() => {
    		return "这是静态方法的第二个 resolve 值";
    	})
    	.then(() => {
    		throw "这是静态方法的第一个 reject 值";
    		return "这是静态方法的第二个 resolve 值";
    	})
    	.done();
    
  6. finally 方法 作用:不管 Promise 对象最后状态如何,都会执行的操作 与 done 方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行

    finally (cb) {
      return this.then(
        value  => MyPromise.resolve(cb()).then(() => value),
        reason => MyPromise.resolve(cb()).then(() => { throw reason })
      );
    };
    

2.1 Generator 定义

  1. Generator 可以理解为一个状态机,内部封装了很多状态,同时返回一个迭代器 Iterator 对象;
  2. 迭代器 Iterator 对象:定义标准方式产生一个有限或无限序列值,迭代器有 next() 对象;
  3. 多次返回可以被 next 多次调用,最大特点是可以控制执行顺序;

2.2 声明方法

是一种特殊的函数

function* gen(x) {
	const y = yield x + 6;
	return y;
}
// yield 如果用在另外一个表达式中,要放在()里面
// 像上面如果是在=右边就不用加()
function* genOne(x) {
	const y = `这是第一个 yield 执行:${yield x + 1}`;
	return y;
}

整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明

2.3 执行

  1. 普通执行

    const g = gen(1);
    //执行 Generator 会返回一个Object,而不是像普通函数返回 return 后面的值
    g.next(); // { value: 7, done: false }
    //调用指针的 next 方法,会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或 return 语句暂停,也就是执行yield 这一行
    // 执行完成会返回一个 Object,
    // value 就是执行 yield 后面的值, done 表示函数是否执行完毕
    g.next(); // { value: undefined, done: true }
    // 因为最后一行 return y 被执行完成,所以 done 为 true
    
  2. next 方法传参数

    const g = gen(1);
    g.next(); // { value: 7, done: false }
    g.next(2); // { value: 2, done: true }
    // next 的参数是作为上个阶段异步任务的返回结果
    
  3. 嵌套执行 必须用到 yield*关键字

    function* genTwo(x) {
    	yield* gen(1);
    	yield* genOne(1);
    	const y = `这是第 二个 yield 执行:${yield x + 2}`;
    	return y;
    }
    const iterator = genTwo(1);
    // 因为 Generator 函数运行时生成的是一个 Iterator 对象,所以可以直接使用 for...of 循环遍历
    for (let value of iterator) {
    	console.log(value); // 7 2 3
    }
    

2.4 yield 和 return 的区别

相同点:

  1. 都能返回语句后面的那个表达式的值
  2. 都可以暂停函数执行

区别:

  1. 一个函数可以有多个 yield,但是只能有一个 return
  2. yield 有位置记忆功能,return 没有

2.5 throw

抛出错误,可以被 try...catch...捕捉到

g.throw'这是抛出的一个错误');
// 这是抛出的一个错误

2.6 应用

// 要求:函数 valOne,valTwo,valThree 依次执行
function* someTask() {
	try {
		const valOne = yield 1;
		const valTwo = yield 2;
		const valThree = yield 3;
		return "fhf";
	} catch (e) {}
}
scheduler(someTask());
function scheduler(task) {
	const taskObj = task.next(task.value);
	console.log(taskObj); // {value: 1, done: false} {value: 2, done: false} {value: 3, done: false}{value: "fhf", done: true}
	// 如果 Generator 函数未结束,就继续调用
	if (!taskObj.done) {
		task.value = taskObj.value;
		scheduler(task);
	}
}

2.7 PolyFill

原理图 img

2.7.1 初级版

实现一个迭代器(Iterator)

// 源码实现
function createIterator(items) {
	var i = 0;
	return {
		next: function () {
			var done = i >= items.length;
			var value = !done ? items[i++] : undefined;
			return {
				done: done,
				value: value,
			};
		},
	};
}

// 应用
const iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

2.7.2 中级版

实现可迭代(Iterable)

  1. 可以通过for...of...遍历的对象,即原型链上有Symbol.iterator属性;

  2. Symbol.iterator:返回一个对象的无参函数,被返回对象符合迭代器协议;

  3. for...of接受一个可迭代对象(Iterable),或者能强制转换/包装成一个可迭代对象的值(如’abc’),遍历时,for...of会获取可迭代对象的Symbol.iterator,对该迭代器逐次调用 next(),直到迭代器返回对象的 done 属性为 true 时,遍历结束,不对该 value 处理;

    const a = ["a", "b", "c", "d", "e"];
    for (let val of a) {
    	console.log(val);
    }
    // 'a' 'b' 'c' 'd' 'e'
    
    // 等价于
    
    const a = ["a", "b", "c", "d", "e"];
    for (let val, ret, it = a[Symbol.iterator](); (ret = it.next()) && !ret.done; ) {
    	val = ret.value;
    	console.log(val);
    }
    // "a" "b" "c" "d" "e"
    
  4. yield* 可返回一个 Iterable 对象;

  5. 源码改造

    function createIterator(items) {
    	let i = 0;
    	return {
    		next: function () {
    			let done = i >= items.length;
    			let value = !done ? items[i++] : undefined;
    			return {
    				done: done,
    				value: value,
    			};
    		},
    		[Symbol.iterator]: function () {
    			return this;
    		},
    	};
    }
    let iterator = createIterator([1, 2, 3]);
    console.log(...iterator); // 1, 2, 3
    

2.7.3 高级版

  1. for...of...接收可迭代对象,能强制转换或包装可迭代对象的值;
  2. 遍历时,for...of 会获取可迭代对象的Symbol.iterator,对该迭代器逐次调用 next(),直到迭代器返回对象的 done 属性为 true 时,遍历结束,不对该 value 处理;
  3. 所以可以利用 for...of...封装到原型链上.
Object.prototype[Symbol.iterator] = function* () {
	for (const key in this) {
		if (this.hasOwnProperty(key)) {
			yield [key, this[key]];
		}
	}
};

3.1 async 作用

  1. async 函数返回的是一个 Promise 对象 在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象

    async function testAsync() {
    	return "hello async";
    }
    
    const result = testAsync();
    console.log(result); //Promise 对象
    
  2. async 和 then async 返回一个 Promise,所以可以通过 then 获取值

    testAsync().then((v) => {
    	console.log(v); // 输出 hello async
    });
    

    所以 async 里面的函数会马上执行,这个就类似 Generator 的*

3.2 await 作用

  1. await 后面可以是 Promise 对象或其他表达式

    function getSomething() {
    	return "something";
    }
    async function testAsync() {
    	return Promise.resolve("hello async");
    }
    async function test() {
    	const v1 = await getSomething();
    	const v2 = await testAsync();
    	console.log(v1, v2); //something 和 hello async
    }
    test();
    
  2. await 后面不是 Promise 对象,直接执行

  3. await 后面是 Promise 对象会阻塞后面的代码,Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果

  4. 所以这就是 await 必须用在 async 的原因,async 刚好返回一个 Promise 对象,可以异步执行阻塞

3.3 async 和 await 结合作用

  1. 主要是处理 Promise 的链式回调或函数的地狱回调 回到 Generator 中要求函数 valOne,valTwo,valThree 函数依次执行

    function valOne() {}
    function valTwo() {}
    function valThree() {}
    
    async () => {
    	await valOne();
    	await valTwo();
    	await valThree();
    };
    
  2. 处理异常 try...catch...或者 await .catch()

3.4 和 Generator 的区别

  1. async 是内置执行器(Generator 函数的执行必须依靠执行器),无需手动执行 next()

  2. 更广的适用性。co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 await 后面可以是任意表达式,都会返回一个 Promise 对象

    // Thunk 函数:是能将执行结果传入回调函数,并将该回调函数返回的函数
    function f(m) {
    	return m * 2;
    }
    f(x + 5);
    
    // 等同于
    
    var thunk = function () {
    	return x + 5;
    };
    function f(thunk) {
    	return thunk() * 2;
    }
    
  3. 返回 Promise,而 Generator 返回 Iterator

  4. async 函数就是 Generator 函数的语法糖 async 就相当于 Generator 的*,await 相当于yield,用法有很多相似之处

3.5 执行器 PolyFill

实现执行器两种方式:

  1. 回调函数(Thunk 函数)
  2. Promise 对象

3.5.1 初级版

async function fn(args) {
	// ...
}

// 等价于

function fn(args) {
	return spawn(function* () {
		// ...
	});
}
function spawn(gen) {
	let g = gen();
	function next(data) {
		let result = g.next(data);
		if (result.done) return result.value;
		result.value.then(function (data) {
			next(data);
		});
	}
	next();
}

3.5.2 高级版

function spawn(genF) {
	//spawn 函数就是自动执行器,跟简单版的思路是一样的,多了 Promise 和容错处理
	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);
		});
	});
}

4.1 Promise,Generator,async 和 await 对比

  1. 代码对比: 场景:假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。

    A. Promise

    function chainAnimationsPromise(elem, animations) {
    	// 变量 ret 用来保存上一个动画的返回值
    	let ret = null;
    	// 新建一个空的 Promise
    	let p = Promise.resolve();
    	// 使用 then 方法,添加所有动画
    	for (let anim of animations) {
    		p = p.then(function (val) {
    			ret = val;
    			return anim(elem);
    		});
    	}
    	// 返回一个部署了错误捕捉机制的 Promise
    	return p
    		.catch(function (e) {
    			/* 忽略错误,继续执行 */
    		})
    		.then(function () {
    			return ret;
    		});
    }
    

    B. Generator

    function chainAnimationsGenerator(elem, animations) {
    	return spawn(function* () {
    		let ret = null;
    		try {
    			for (let anim of animations) {
    				ret = yield anim(elem);
    			}
    		} catch (e) {
    			/* 忽略错误,继续执行 */
    		}
    		return ret;
    	});
    }
    

    C. async & await

    async function chainAnimationsAsync(elem, animations) {
    	let ret = null;
    	try {
    		for (let anim of animations) {
    			ret = await anim(elem);
    		}
    	} catch (e) {
    		/* 忽略错误,继续执行 */
    	}
    	return ret;
    }
    

对比可以看出async...await...代码更优雅

4.2 原理

async 和 await 是在 Generator 的基础上封装了自执行函数和一些特性; 具体对比见每个 API 的 PolyFill

4.3 执行顺序

来道头条的面试

async function async1() {
	await async2();
	console.log("async1 end");
}
async function async2() {
	console.log("async2 end");
}
console.log("script start");
async1();
setTimeout(function () {
	console.log("setTimeout");
}, 0);
new Promise((resolve) => {
	console.log("Promise");
	resolve();
})
	.then(function () {
		console.log("promise1");
	})
	.then(function () {
		console.log("promise2");
	});
console.log("script end");

// 旧版 Chrome 打印
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout

// 新版 Chrome 打印
// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout

// 这里面其他的顺序除了 async1 end , promise1 , promise2 这几个顺序有点争议,其他应该没有什么问题

// 新版是因为 V8 团队将最新的规范进行了修改,await 变得更快了,这道题细节分析不再赘述,上面原理都有讲到

转载分析:

如果 async 关键字函数返回的不是 promise,会自动用Promise.resolve()包装

javascript 自上而下依次执行:

  1. async1 函数声明
  2. async2 函数声明
  3. 打印"script start"
  4. 执行 async1 函数
    1. 执行 async2 函数
      1. 打印"async2 end"
    2. await async2 函数,阻塞代码,不继续执行
  5. 执行 setTimeout 函数,放入宏任务队列待执行
  6. 执行 new Promise
    1. 打印"Promise"
    2. 执行 then 函数,放入微任务队列待执行
  7. 打印"script end"
  8. 执行 await 阻塞代码。带有 async 关键字的函数仅仅是把 return 值包装成了 promise,这里的 await Promise.resolve() 就类似于Promise.resolve(undefined).then((undefined) => {})。所以将(undefined) => {}放入微任务待执行。
  9. 打印"async1 end"
  10. 第一轮宏任务执行完毕,执行微任务队列。先进先出,依次执行console.log("promise1");console.log("promise2");(undefined) => {}
  11. 执行宏任务队列中的console.log("setTimeout");