手写实现Promise
Promise
是一个优秀的异步解决方案,现在主流浏览器上都已经实现了对Promise的支持
不使用内置 promise
,实现一个满足 Promises/A+ 规范 的 MyPromise
。
以下方法都不在
Promise/A+
里面,但是是在ES6
的官方Promise
的API
里,且这些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
值(包括undefined
、thenable
或promise
)。exception
是使用throw
语句抛出的值。reason
是一个值,表示一个promise
被拒绝的原因。
2、要求
Promise 状态
Promise
必须处于以下三种状态之一:pending
,fulfilled
, orrejected
。·
- 处于
pending
状态时,Promise
可以转换到fulfilled
或rejected
状态。 - 处于
fulfilled
状态时,Promise
不得转换到其它状态。- 必须有一个
value
,并且这个值一定不能改变。
- 必须有一个
- 处于
rejected
状态时,Promise
不得转换到其它状态。- 必须有一个
reason
,并且这个值一定不能改变。
- 必须有一个
这里的不得转换意味着其不可变的属性(即 ===
),但并不意味着深度不变(深度嵌套的值可变)。
then
方法
Promise
必须提供 then
方法来访问其当前或最终的 value
或 reason
。
Promise
的 then
方法接受两个参数:
promise.then(onFulfilled, onRejected)
onFulfilled
和 onRejected
都是可选参数,并且如果它们不是函数,则必须忽略它。
- 如果
onFulfilled
是一个函数- 它必须在
promise
完成后调用,promise
的value
作为它的第一个参数 - 在
promise
完成之前不能调用它 - 不能多次调用它。
- 它必须在
- 如果
onRejected
是一个函数- 它必须在
promise
被拒绝后调用,promise
的reason
是它的第一个参数 - 在
promise
被拒绝之前不能调用它 - 不能多次调用它
- 它必须在
- 在执行
execution context
堆栈(仅包含平台代码)之前,不得调用onFulfilled
或onRejected
。[注意事项第一点] onFulfilled
和onRejected
必须作为函数调用(即没有this
值)。[注意事项第二点]then
可能会在同一个Promise
上被多次调用- 当
Promise
被实现,所有相应的onFulfilled
回调必须按照它们对then
的调用顺序执行。 - 当
promise
被拒绝时,所有相应的onRejected
回调必须按照它们对then
的发起调用的顺序执行。
- 当
then
必须返回一个promise
[注意事项第三点]promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled
或onRejected
返回一个值x
,运行Promise
解决程序[[Resolve]](promise2, x)
。 - 如果
onFulfilled
或onRejected
抛出一个意外e
,promise2
必须以e
为reason
被rejected
。 - 如果
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)
,执行以下步骤:
- 如果
promise
和x
指向同一个对象,则以TypeError
作为原因拒绝promise
- 如果
x
是一个promise
,采用它的状态:[注意事项第四点]- 如果
x
处于pending
,则Promise
必须保持pending
,直到x
变为fulfilled
或rejected
- 当
x
是fulfilled
,使用相同的value
实现promise
- 当
x
是rejected
,以同样的reason
拒绝promise
- 如果
- 除此之外,如果
x
是一个对象或函数- 令
then
为x.then
[注意事项第五点] - 如果检索属性
x.then
导致抛出异常e
,则以e
为value
拒绝promise
- 如果
then
是一个函数,则使用x
作为this
、第一个参数resolvePromis
e 和第二个参数rejectPromise
调用它,其中:- 使用
value
y
调用resolvePromise
时,运行[[Resolve]](promise, y)
- 使用
reason
r
调用rejectPromise
时,使用r
拒绝promise
- 如果同时调用了
resolvePromise
和rejectPromise
,或者对同一个参数进行了多次调用,则第一次调用优先,并且任何进一步的调用都将被忽略。 - 如果调用
then
抛出异常e
:- 如果已调用
resolvePromise
或rejectPromise
,则忽略它。 - 否则,以
e
为reason
拒绝promise
- 如果已调用
- 使用
- 如果
then
不是函数,则用x
实现promise
- 令
- 如果
x
不是对象或函数,使用x
实现promise
如果一个参与了 thenable
循环链的 thenable
去 resolve promise
,这样 [[Resolve]](promise, thenable)
的递归性质最终会导致 [[Resolve]](promise, thenable)
会被再次调用,遵循上述算法将会导致无限递归。我们鼓励去实现(但不是必需的)检测这样的递归,并以 TypeError
作为 reason
去 reject Promise
。[注意事项第六点]
3、注意事项
- 这里的“平台代码”指的是引擎,环境和 promise 实现代码。实际上,这个要求保证了
onFulfilled
和onRejected
将会异步执行,在事件循环之后,用一个新的堆栈来调用它。 这可以通过“宏任务”机制(如settimeout
或setimmediate
)或“微任务”机制(如mutationobserver
或process.nextick
)来实现。由于Promise
实现被视为平台代码,因此它本身可能包含一个任务调度队列或“trampoline
”,并在其中调用处理程序。 - 也就是说,在 strict 模式下,这(指的是this)在它们内部将会是 undefined;在普通模式下,它将会是全局对象。
- 如果实现满足所有要求,则实现可能允许
promise2 == promise1
。每个实现都应该记录它是否能够生成promise2 == promise1
以及在什么条件下。 - 一般来说,只有当
X
来自当前的实现时,才知道它是一个真正的promise
。本条款允许使用特定于实现的方法来采用已知一致承诺的状态。 - 此过程首先存储对 x 的引用,然后测试该引用,然后调用该引用,避免多次访问
x.then
属性。这些预防措施对于确保访问器属性的一致性非常重要,访问器属性的值可能在两次检索之间发生更改。 - 实现方式中不应当在
thenbale
链中的深度设置主观的限制,并且不应当假设链的深度超过主观的限制后会是无限的。只有真正的循环才能导致TypeError
。如果遇到由无限多个不同thenable
组成的链,那么永远递归是正确的行为。