Skip to main content

手写实现Promise

Promise 是一个优秀的异步解决方案,现在主流浏览器上都已经实现了对Promise的支持

不使用内置 promise ,实现一个满足 Promises/A+ 规范MyPromise

以下方法都不在Promise/A+里面,但是是在ES6的官方PromiseAPI里,且这些API都是对其的封装。这里顺便实现以下。

  • Promise.resolve
  • Promise.reject
  • Promise.all
  • Promise.race
  • Promise.prototype.catch
  • Promise.prototype.finally

我的github库:MyPromise

function版本

首先是 MyPromise 函数本体:

function MyPromise(executor) {
// pending fulfilled rejected
this._status = "pending";
// value
this._value;
// reason
this._reason;

// resolve fns and reject fns
this._resolveQueue = [];
this._rejectQueue = [];

const resolve = (val) => {
// 注意 this 指向问题!!!
if(this._status === "pending") {
this._status = "fulfilled";
this._value = val;
this._resolveQueue.forEach(fn => fn())
}
}
const reject = (reason) => {
if(this._status === "pending") {
this._status = "rejected";
this._reason = reason;
this._rejectQueue.forEach(fn => fn())
}
}
try {
executor(resolve, reject);
} catch(e) {
reject(e);
}
}

根据提示,写 promise 的解决程序

function ResolveMyPromise(promise,x,resolve,reject){
if(promise === x){ // promise 和 x 指向同一个对象
return reject(new TypeError('chaining cycle'));
}
let hasCalled = false;
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
try{
let fn = x.then;
if(typeof fn === 'function'){
// 使用 call 保持this指向
fn.call(x, a => {
if(hasCalled) return;
hasCalled = true;
ResolveMyPromise(promise, a, resolve, reject);
},e => {
if(hasCalled) return;
hasCalled = true;
reject(e);
});
}else{
resolve(x);
}
} catch(e) {
if(hasCalled) return;
hasCalled = true;
reject(e);
}
} else {
resolve(x);
}
}

然后就可以实现 then 方法:

// 加在原型链上的方法 - 实例化的对象都共用
MyPromise.prototype.then = function (resolveFn, rejectFn) {
resolveFn = typeof resolveFn === 'function' ? resolveFn : val=>val;
rejectFn = typeof rejectFn === 'function' ? rejectFn : e => { throw e };
let self = this, promise2;
promise2 = new MyPromise( (resolve, reject) => {
if (self._status === 'fulfilled') {
setTimeout(()=>{
try {
ResolveMyPromise(promise2, resolveFn(self._value) , resolve, reject);
} catch (e) {
reject(e);
}
},0)
}
if (self._status === 'rejected') {
setTimeout(()=>{
try {
ResolveMyPromise(promise2, rejectFn(self._reason), resolve, reject);
} catch (e) {
reject(e);
}
},0)
}
if (self._status === 'pending') {
self._resolveQueue.push(() => {
setTimeout(()=>{
try {
ResolveMyPromise(promise2, resolveFn(self._value), resolve, reject);
} catch (e) {
reject(e);
}
},0)
});
self._rejectQueue.push(() => {
setTimeout(()=>{
try {
ResolveMyPromise(promise2, rejectFn(self._reason), resolve, reject);
} catch (e) {
reject(e);
}
},0)
});
}
});
return promise2;
}

实现其它方法:

//catch方法其实就是执行一下then的第二个回调
MyPromise.prototype.catch = function (rejectFn) {
return this.then(null, rejectFn)
}
// 在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。
// 在finally之后,还可以继续then。并且会将值原封不动的传递给后面的then
MyPromise.prototype.finally = function(callback) {
return this.then(val => {
return MyPromise.resolve(callback()).then(() => val)
}, e => {
return MyPromise.reject(callback()).then(() => {
throw e;
})
})
}


// 加在属性上的方法 - 实例化的对象没有该方法,相当于OOP的静态方法
MyPromise.resolve = (val) => {
return new MyPromise((res, rej) => {
res(val);
})
}
MyPromise.reject = (reason) => {
return new MyPromise((res, rej) => {
rej(reason);
})
}
MyPromise.catch = function(onRejected) {
return this.then(null, onRejected);
}
MyPromise.all = function(promises) {
let i = 0, ans = [];
return new MyPromise((resolve, reject) => {
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(val => {
i++;
ans[index] = val;
if(i === promises.length) {
resolve(ans);
}
}, e => {
reject(e);
})
})
})
}
MyPromise.race = function(promises) {
return new MyPromise((res, rej) => {
for(const promise of promises) {
MyPromise.resolve(promise).then(val => {
res(val);
}, e => {
rej(e);
})
}
})
}

class版本

代码解释以后补上,这里直接贴代码

class NewPromise {
constructor(executor) {
this._value;
this._reason;
this._state = "pending";
this._resolveFns = [];
this._rejectFns = [];
let _resolve = (val) => {
if(this._state === "pending") {
this._value = val;
this._state = "fulfilled";
this._resolveFns.forEach(fn => fn());
}
}
let _reject = (reason) => {
if(this._state === "pending") {
this._reason = reason;
this._state = "rejected";
this._rejectFns.forEach(fn => fn());
}
}
executor(_resolve, _reject);
}
then(resolveFn, rejectFn) {
resolveFn = typeof resolveFn === 'function' ? resolveFn : val => val;
rejectFn = typeof rejectFn === 'function' ? rejectFn : reason => { throw reason };
let self = this, promise2;
promise2 = new NewPromise((resolve, reject) => {
if(self._state === "pending") {
self._resolveFns.push(() => {
setTimeout(()=>{
try {
self.resolveMyPromise(promise2, resolveFn(self._value), resolve, reject);
} catch (e) {
reject(e);
}
},0)
});
self._rejectFns.push(() => {
setTimeout(()=>{
try {
self.resolveMyPromise(promise2, rejectFn(self._reason), resolve, reject);
} catch (e) {
reject(e);
}
},0)
});
}
if (self._state === 'fulfilled') {
setTimeout(()=>{
try {
self.resolveMyPromise(promise2, resolveFn(self._value) , resolve, reject);
} catch (e) {
reject(e);
}
},0)
}
if (self._state === 'rejected') {
setTimeout(()=>{
try {
self.resolveMyPromise(promise2, rejectFn(self._reason), resolve, reject);
} catch (e) {
reject(e);
}
},0)
}
})
return promise2;
}
resolveMyPromise(promise, x, resolve, reject) {
if(x === promise) {
return reject(new TypeError("chaining cycle"));
}
let hasCalled = false, self = this;
if(x != null && (typeof x === 'function' || typeof x === 'object')){
try {
let fn = x.then;
if(typeof fn === 'function') {
fn.call(x, val => {
if(hasCalled) return;
hasCalled = true;

self.resolveMyPromise(promise, val, resolve, reject);

}, reason => {
if(hasCalled) return;
hasCalled = true;
reject(reason);
})
} else {
resolve(x)
}
} catch(reason) {
if(hasCalled) return;
hasCalled = true;
reject(reason);
}
} else {
resolve(x);
}
}
// more
catch(rejectFn) {
return this.then(null, rejectFn)
}
finally(callback) {
return this.then(
value => NewPromise.resolve(callback()).then(() => value),
reason => NewPromise.resolve(callback()).then(() => { throw reason })
)
}
static resolve(value) {
return new NewPromise((resolve, reject) => resolve(value))
}
static reject(reason) {
return new NewPromise((resolve, reject) => reject(reason))
}
static all(promiseArr) {
let index = 0
let result = []
return new NewPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
NewPromise.resolve(p).then(val => {
index++
result[i] = val
if(index === promiseArr.length) {
resolve(result)
}
},
reason => {
reject(reason)
})
})
})
}
static race(promiseArr) {
return new NewPromise((resolve, reject) => {
for (let p of promiseArr) {
NewPromise.resolve(p).then(value => {
resolve(value)
},
reason => {
reject(reason)
})
}
})
}
}

测试

使用到库:promises-tests

一共需要通过872条测试

测试过程输出的信息过多,可以考虑输出信息到文件(重定向到文件 > pass.log

参考资料

Promises/A+ 规范

以下翻译自官网:promisesaplus.com/

Promise 代表异步操作的最终结果。与 Promise 交互的主要方式是通过其 then 方法,该方法注册回调以接收 Promise 的最终值或 Promise 失败的原因。

该规范详细说明了 then 方法的行为,提供了一个可互操作的基础,所有符合 Promises/A+Promise 实现都可以依赖该基础来提供。因此,规范应该被认为是非常稳定的。尽管 Promises/A+ 组织可能偶尔会通过微小的向后兼容更改来修改此规范以解决新发现的极端情况,但只有在仔细考虑、讨论和测试后,我们才会集成大的或向后不兼容的更改。

最后,核心 Promises/A+ 规范不涉及如何创建、履行或拒绝 Promise,而是选择专注于提供可互操作的 then 方法。

1、术语

  • promise 是具有 then 方法的对象或函数,其行为符合本规范。
  • thenable是定义 then 方法的对象或函数。
  • value是任何合法的 JavaScript 值(包括 undefinedthenablepromise)。
  • exception是使用 throw 语句抛出的值。
  • reason 是一个值,表示一个 promise 被拒绝的原因。

2、要求

Promise 状态

Promise 必须处于以下三种状态之一: pending, fulfilled, or rejected。·

  1. 处于 pending 状态时,Promise 可以转换到fulfilledrejected 状态。
  2. 处于 fulfilled 状态时,Promise 不得转换到其它状态。
    • 必须有一个value,并且这个值一定不能改变。
  3. 处于 rejected 状态时,Promise 不得转换到其它状态。
    • 必须有一个reason,并且这个值一定不能改变。

这里的不得转换意味着其不可变的属性(即 ===),但并不意味着深度不变(深度嵌套的值可变)。

then 方法

Promise 必须提供 then 方法来访问其当前或最终的 valuereasonPromisethen 方法接受两个参数:

promise.then(onFulfilled, onRejected)

onFulfilledonRejected 都是可选参数,并且如果它们不是函数,则必须忽略它。

  • 如果 onFulfilled 是一个函数
    • 它必须在 promise 完成后调用,promisevalue 作为它的第一个参数
    • promise 完成之前不能调用它
    • 不能多次调用它。
  • 如果 onRejected 是一个函数
    • 它必须在 promise 被拒绝后调用,promisereason 是它的第一个参数
    • promise 被拒绝之前不能调用它
    • 不能多次调用它
  • 在执行 execution context 堆栈(仅包含平台代码)之前,不得调用 onFulfilledonRejected[注意事项第一点]
  • onFulfilledonRejected 必须作为函数调用(即没有 this 值)。[注意事项第二点]
  • then 可能会在同一个 Promise 上被多次调用
    • Promise 被实现,所有相应的 onFulfilled 回调必须按照它们对 then 的调用顺序执行。
    • promise 被拒绝时,所有相应的 onRejected 回调必须按照它们对 then 的发起调用的顺序执行。
  • then 必须返回一个 promise[注意事项第三点]
    promise2 = promise1.then(onFulfilled, onRejected);
    • 如果 onFulfilledonRejected 返回一个值 x,运行Promise解决程序 [[Resolve]](promise2, x)
    • 如果 onFulfilledonRejected 抛出一个意外 epromise2 必须以 ereasonrejected
    • 如果 onFulfilled 不是一个函数并且 promise1 处于 fulfilled 状态,promise2 必须以与 promise1 同样的 value 转变到 fulfilled 状态。
    • 如果 onRejected 不是一个函数并且 promise1 处于 rejected 状态,promise2 必须以与 promise1 同样的 reason 转变到 rejected状态。

Promise 解决程序

promise解决程序是一个抽象的操作,它把一个 promise 和一个 value 作为输入,我们将这个表示为 [[Resolve]](promise, x)。如果 x 是一个 thenable ,它将会试图让 promise 采用 x 的状态,前提是x的行为至少有点像一个 promise。否则,它将会用值 x 执行 promise。 对这些 thenable 的处理使得与 promise 实现方式能够去互相操作。只要它们公开了符合 Promise/A+then 方法。它还使得 promises/A+ 实现方式能够采用合理的 then 方法去“同化”不一致的实现方式。 为了运行[[Resolve]](promise, x),执行以下步骤:

  1. 如果 promisex 指向同一个对象,则以 TypeError 作为原因拒绝 promise
  2. 如果 x 是一个 promise,采用它的状态:[注意事项第四点]
    1. 如果 x 处于pending,则 Promise 必须保持pending,直到 x 变为 fulfilledrejected
    2. xfulfilled,使用相同的 value 实现 promise
    3. xrejected,以同样的 reason 拒绝 promise
  3. 除此之外,如果 x 是一个对象或函数
    1. thenx.then[注意事项第五点]
    2. 如果检索属性 x.then 导致抛出异常 e,则以 evalue 拒绝 promise
    3. 如果 then 是一个函数,则使用 x 作为 this、第一个参数 resolvePromise 和第二个参数 rejectPromise 调用它,其中:
      • 使用 value y 调用 resolvePromise 时,运行 [[Resolve]](promise, y)
      • 使用 reason r 调用 rejectPromise 时,使用 r 拒绝 promise
      • 如果同时调用了 resolvePromiserejectPromise,或者对同一个参数进行了多次调用,则第一次调用优先,并且任何进一步的调用都将被忽略。
      • 如果调用 then 抛出异常 e:
        • 如果已调用 resolvePromiserejectPromise,则忽略它。
        • 否则,以 ereason 拒绝 promise
    4. 如果 then 不是函数,则用 x 实现 promise
  4. 如果 x 不是对象或函数,使用 x 实现promise

如果一个参与了 thenable 循环链的 thenableresolve promise,这样 [[Resolve]](promise, thenable) 的递归性质最终会导致 [[Resolve]](promise, thenable) 会被再次调用,遵循上述算法将会导致无限递归。我们鼓励去实现(但不是必需的)检测这样的递归,并以 TypeError 作为 reasonreject Promise[注意事项第六点]

3、注意事项

  1. 这里的“平台代码”指的是引擎,环境和 promise 实现代码。实际上,这个要求保证了 onFulfilledonRejected 将会异步执行,在事件循环之后,用一个新的堆栈来调用它。 这可以通过“宏任务”机制(如 settimeoutsetimmediate )或“微任务”机制(如 mutationobserverprocess.nextick)来实现。由于 Promise 实现被视为平台代码,因此它本身可能包含一个任务调度队列或“trampoline”,并在其中调用处理程序。
  2. 也就是说,在 strict 模式下,这(指的是this)在它们内部将会是 undefined;在普通模式下,它将会是全局对象。
  3. 如果实现满足所有要求,则实现可能允许 promise2 == promise1。每个实现都应该记录它是否能够生成 promise2 == promise1 以及在什么条件下。
  4. 一般来说,只有当 X 来自当前的实现时,才知道它是一个真正的 promise。本条款允许使用特定于实现的方法来采用已知一致承诺的状态。
  5. 此过程首先存储对 x 的引用,然后测试该引用,然后调用该引用,避免多次访问 x.then 属性。这些预防措施对于确保访问器属性的一致性非常重要,访问器属性的值可能在两次检索之间发生更改。
  6. 实现方式中不应当在 thenbale 链中的深度设置主观的限制,并且不应当假设链的深度超过主观的限制后会是无限的。只有真正的循环才能导致TypeError。如果遇到由无限多个不同 thenable 组成的链,那么永远递归是正确的行为。