手写实现Promise
Promise是一个优秀的异步解决方案,现在主流浏览器上都已经实现了对Promise的支持
不使用内置 promise ,实现一个满足 Promises/A+ 规范 的 MyPromise 。
以下方法都不在
Promise/A+里面,但是是在ES6的官方Promise的API里,且这些API都是对其的封装。这里顺便实现以下。
Promise.resolvePromise.rejectPromise.allPromise.racePromise.prototype.catchPromise.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、第一个参数resolvePromise 和第二个参数rejectPromise调用它,其中:- 使用
valuey调用resolvePromise时,运行[[Resolve]](promise, y) - 使用
reasonr调用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组成的链,那么永远递归是正确的行为。