异步在实现上,依赖一些特殊的语法规则。从整体上来说,异步方案经历了如下的四个进化阶段:
回调函数 —> Promise —> Generator —> async/await。
1、promise
Promise 会接收一个执行器,在这个执行器里,我们需要把目标的异步任务给”填进去“。
在 Promise 实例创建后,执行器里的逻辑会立刻执行,在执行的过程中,根据异步返回的结果,决定如何使用 resolve 或 reject 来改变 Promise实例的状态。 Promise 实例有三种状态:
• pending 状态,表示进行中。这是 Promise 实例创建后的一个初始态;
• fulfilled 状态,表示成功完成。这是我们在执行器中调用 resolve 后,达成的状态;
• rejected 状态,表示操作失败、被拒绝。这是我们在执行器中调用 reject后,达成的状态。
当我们用 resolve 切换到了成功态后,Promise 的逻辑就会走到 then 中的传入的方法里去;用 reject 切换到失败态后,Promise 的逻辑就会走到 catch 传入的方法中去。
2、Generator
Generator 一个有利于异步的特性是,它可以在执行中被中断、然后等待一段时间再被我们唤醒。通过这个“中断后唤醒”的机制,我们可以把 Generator看作是异步任务的容器,利用 yield 关键字,实现对异步任务的等待。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
const co = require('co');
co(httpGenerator());
这里的 co,大家就可以把它看作是一个加强版的 runGenerator。我们只需要在代码里引入 co 库,然后把写好的 generator 传进去,就可以轻松地实现 generator 异步了。
3、Async/Await
首先,我们用 async 关键字声明一个函数为“异步函数”:
async function httpRequest() {
}
然后,我们就可以在这个函数内部使用 await 关键字了:
async function httpRequest() {
let res1 = await httpPromise(url1)
console.log(res1)
}
这个 await 关键字很绝,它的意思就是“我要异步了,可能会花点时间,后面的语句都给我等着”。当我们给 httpPromise(url1) 这个异步任务应用了 await 关键字后,整个函数会像被“yield”了一样,暂停下来,直到异步任务的结果返回后,它才会被“唤醒”,继续执行后面的语句。
注:async/await 和 generator 方案,相较于 Promise 而言,有一个重要的优势:Promise 的错误需要通过回调函数捕获,try catch 是行不通的。而 async/await 和 generator 允许 try/catch。这也是一个可以作为命题点细节,大家留心把握。
4、Promise命题思路
1.问:说说你理解的 Promise
Promise 对象是一个代理对象。它接受你传入的 executor(执行器)作为入参,允许你把异步任务的成功和失败分别绑定到对应的处理方法上去。一个 Promise 实例有三种状态:
• pending 状态,表示进行中。这是 Promise 实例创建后的一个初始态;
• fulfilled 状态,表示成功完成。这是我们在执行器中调用 resolve 后,达成的状态;
• rejected 状态,表示操作失败、被拒绝。这是我们在执行器中调用 reject后,达成的状态;
Promise实例的状态是可以改变的,但它只允许被改变一次。当我们的实例状态从 pending 切换为 rejected 后,就无法再扭转为fulfilled,反之同理。当 Promise 的状态为 resolved 时,会触发其对应的 then 方法入参里的 onfulfilled 函数;当 Promise 的状态为rejected 时,会触发其对应的 then 方法入参里的 onrejected 函数。
2.Promise 的出现是为了解决什么问题?
回调地狱问题
3.Promise 常见方法有哪些?各自是干嘛的?
Promise的方法有以下几种:
• Promise.all(iterable):这个方法返回一个新的 promise 对象,该 promise 对象在 iterable 参数对象里所有的 promise 对象都成功的时候才会触发成功,一旦有任何一个 iterable 里面的 promise 对象失败则立即触发该 promise 对象的失败。
举个?:
var p1 = Promise.resolve('1号选手');
var p2 = '2号选手';
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "3号选手");
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // ["1号选手", "2号选手", "3号选手"]
});
• Promise.race(iterable):当 iterable 参数里的任意一个子 promise 被成功或失败后,父 promise 马上也会用子 promise 的成功返回值或失败详情作为参数调用父 promise 绑定的相应处理函数,并返回该 promise 对象。
举个?:
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "1号选手");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 50, "2号选手");
});
// 这里因为 2 号选手返回得更早,所以返回值以 2号选手为准
Promise.race([p1, p2]).then(function(value) {
console.log(value); // "2号选手"
});
Promise.reject(reason): 返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法
Promise.resolve(value):它返回一个 Promise 对象,但是这个对象的状态由你传入的value决定,情形分以下两种:
如果传入的是一个带有 then 方法的对象(我们称为 thenable 对象),返回的Promise对象的最终状态由then方法执行决定
否则的话,返回的 Promise 对象状态为 fulfilled,同时这里的 value 会作为 then 方法中指定的 onfulfilled 的入参
then 方法的入参只能是函数
4.手写Promise
function CutePromise(executor) {
// value 记录异步任务成功的执行结果
this.value = null;
// reason 记录异步任务失败的原因
this.reason = null;
// status 记录当前状态,初始化是 pending
this.status = 'pending';
// 缓存两个队列,维护 resolved 和 rejected 各自对应的处理函数
this.onResolvedQueue = [];
this.onRejectedQueue = [];
// 把 this 存下来,后面会用到
var self = this;
// 定义 resolve 函数
function resolve(value) {
// 如果不是 pending 状态,直接返回
if (self.status !== 'pending') {
return;
}
// 异步任务成功,把结果赋值给 value
self.value = value;
// 当前状态切换为 resolved
self.status = 'resolved';
// 用 setTimeout 延迟队列任务的执行
setTimeout(function(){
// 批量执行 resolved 队列里的任务
self.onResolvedQueue.forEach(resolved => resolved(self.value));
});
}
// 定义 reject 函数
function reject(reason) {
// 如果不是 pending 状态,直接返回
if (self.status !== 'pending') {
return;
}
// 异步任务失败,把结果赋值给 value
self.reason = reason;
// 当前状态切换为 rejected
self.status = 'rejected';
// 用 setTimeout 延迟队列任务的执行
setTimeout(function(){
// 批量执行 rejected 队列里的任务
self.onRejectedQueue.forEach(rejected => rejected(self.reason));
});
}
// 把 resolve 和 reject 能力赋予执行器
executor(resolve, reject);
}
// then 方法接收两个函数作为入参(可选)
CutePromise.prototype.then = function(onResolved, onRejected) {
// 注意,onResolved 和 onRejected必须是函数;如果不是,我们此处用一个透传来兜底
if (typeof onResolved !== 'function') {
onResolved = function(x) {return x};
}
if (typeof onRejected !== 'function') {
onRejected = function(e) {throw e};
}
// 依然是保存 this
var self = this;
// 判断是否是 resolved 状态
if (self.status === 'resolved') {
// 如果是 执行对应的处理方法
onResolved(self.value);
} else if (self.status === 'rejected') {
// 若是 rejected 状态,则执行 rejected 对应方法
onRejected(self.reason);
} else if (self.status === 'pending') {
// 若是 pending 状态,则只对任务做入队处理
self.onResolvedQueue.push(onResolved);
self.onRejectedQueue.push(onRejected);
}
return this
};
5、Promise/A+ —— 决议过程