promiseA+规范
let p = new Promise((resolve,reject)=>{
try {
...
resolve(val)
} catch(err) {
reject(err)
}
})
p.then((val)=>{
...
})
按照基本的用法,promise的基本结构应该是这样的
class Promise{
// 构造器
constructor(executor){
let resolve = () => { };
let reject = () => { };
executor(resolve, reject);
}
then(onFulfilled,onRejected){}
}
解释一下:
- promise接受一个函数作为参数,规范里叫executor(执行者的意思)
- executor这个函数接受两个函数作为参数,分别是resolve和reject,这两个函数实际上是在promise函数内部定义的,具体的实现和作用请继续往下看
规范在很大程度上表示了promise的设计原理,我们按照A+规范一步步完善它。也许在这个过程中你可能会对其中一些实现产生疑问,但是不要着急,等到完整的实现之后你就会恍然大悟
Promise 的状态
- Pendding: 初始状态,并可以转化为fulfilled(成功态)和rejected(失败态)
- Fulfilled: 成功状态,不可转化为其他状态,且必须有一个不可改变的值(value)
- Rejected:失败状态,不可转化为其他状态,且必须有一个不可改变的据因(reason)
promise1.0
class MyPromise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
}
};
// 如果executor执行报错,直接执行reject
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
}
then方法
- 一个 promise 必须提供一个
then方法以访问其当前值、终值和据因。promise 的then方法接受两个参数:
promise.then(onFulfilled, onRejected)
then方法可以被同一个promise调用多次
当状态为fulfilled(成功态)时执行onFulfilled,当状态为rejected时,执行onRejected,那么结合then的调用方式,可以将then这样实现:
class MyPromise {
constructor(executor){...}
then(onFulfilled,onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value);
};
if (this.state === 'rejected') {
onRejected(this.reason);
};
}
}
实现异步调用
这样已经实现了一个promise的基本雏形,但是现在它还不能实现异步调用,例如:
new MyPromise((resolve,reject)=>{
setTimeout(()=>{
console.log(123)
resolve()
},100)
}).then(()=>{
console.log(456)
})
你会发现输出也不符合预期,因为这里的resolve是异步执行的,所以resolve是在then之后发生的,state还是pendding。所以我们作出以下改造
class Promise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn()); //
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled,onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value);
};
if (this.state === 'rejected') {
onRejected(this.reason);
};
// 当状态state为pending时,将两个函数存起来
if (this.state === 'pending') {
this.onResolvedCallbacks.push(()=>{
onFulfilled(this.value);
})
this.onRejectedCallbacks.push(()=>{
onRejected(this.reason);
})
}
}
}
仿照发布订阅的思想,使用一个数组将then里面的两个函数储存起来,等异步任务开始的时候,在执行这两个函数。为什么使用数组呢?因为规范中还有一条就是:一个promise可以调用多个then,所以每次调用then的时候将这个函数参数push到数组中,resolve或者reject时foreach执行他们,例如:
// 多个then的情况
let p = new Promise();
p.then();
p.then();
另外我们必须保证then中的两个函数参数onFulfilled和onRejected是最后执行的,因此我们应该把它们放在异步队列当中,实现方法就是用setTimeout包起来即可
...
if (this.state === 'pending') {
this.onResolvedCallbacks.push(()=>{
setTimeout(()=>{
onFulfilled(this.value);
},0)
})
this.onRejectedCallbacks.push(()=>{
setTimeout(()=>{
onRejected(this.reason);
},0)
})
}
链式调用
promise最大的作用就是链式调用,是为了解决回调地狱的问题。接下来实现链式调用
实现的原理就是在then函数中返回一个promise,即
promise2 = promise1.then(onFulfilled, onRejected);
按照A+规范:
- 如果
onFulfilled或者onRejected返回一个值x,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x) - 如果
onFulfilled或者onRejected抛出一个异常e,则promise2必须拒绝执行,并返回拒因e - 如果
onFulfilled不是函数且promise1成功执行,promise2必须成功执行并返回相同的值 - 如果
onRejected不是函数且promise1拒绝执行,promise2必须拒绝执行并返回相同的据因
那么按照规范,我们应该比较x和promise的值,因此我们应该实现一个resolvePromise(promise,x,resolve,reject) 这样一个函数,
因此then方法实现应该是这样的:
···
let promise2 = new Promise((resolve, reject)=>{
if (this.state === 'fulfilled') {
let x = onFulfilled(this.value);
// resolvePromise函数,处理自己return的promise和默认的promise2的关系
resolvePromise(promise2, x, resolve, reject);
};
if (this.state === 'rejected') {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(()=>{
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
})
this.onRejectedCallbacks.push(()=>{
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
})
}
});
// 返回promise,完成链式
return promise2;
}
}
接下来主要就是resolvePromise的实现过程:
规范中规定了三种情况:x与promise相等;x为promise;x为对象或者函数。
Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),运行 [[Resolve]](promise, x) 需遵循以下步骤:【参考Promise 解决过程】。
x 与 promise 相等
如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise。
- x 为 Promise
- 如果 x 为 Promise ,则使 promise 接受 x 的状态。
- 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝。
- 如果 x 处于执行态,用相同的值执行 promise。
- 如果 x 处于拒绝态,用相同的据因拒绝 promise。
- x 为对象或函数
如果 x 为对象或者函数:
- 把 x.then 赋值给 then。
- 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise。
- 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
- 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
- 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
- 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
- 如果调用 then 方法抛出了异常 e:
- 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
- 否则以 e 为据因拒绝 promise
- 如果 then 不是函数,以 x 为参数执行 promise
- 如果 x 不为对象或者函数,以 x 为参数执行 promise
如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise 。
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('循环引用'));
}
if (x instanceof Promise) {
if (x.state === PENDING) {
x.then(
y => {
resolvePromise(promise2, y, resolve, reject);
},
reason => {
reject(reason);
}
);
} else {
x.then(resolve, reject);
}
} else if (x && (typeof x === 'function' || typeof x === 'object')) {
// 避免多次调用
let called = false;
try {
//把 x.then 赋值给 then
let then = x.then;
if (typeof then === 'function') {
// 如果 then 是函数,将 x 作为函数的作用域 this 调用之。
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
// 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行[[Resolve]](promise, y)
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
r => {
if (called) return;
called = true;
reject(r);
}
);
}else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} catch (e) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
// 如果调用 then 方法抛出了异常 e:
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
// 否则以 e 为据因拒绝 promise
if (called) return;
called = true;
reject(e);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
最后我们来完成promise的其他api:
Promise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}
//race方法
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data,resolve){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let j=0;j<promises.length;j++){
promises[i].then(data=>{
processData(j,data,resolve);
},reject);
};
});
}
参考:
juejin.cn/post/684490…
juejin.cn/post/684490…
juejin.cn/post/684490…