Promise原理—一步一步实现一个Promise

时间:2023-03-09 10:04:59
Promise原理—一步一步实现一个Promise

promise特点

一个promise的当前状态只能是pending、fulfilled和rejected三种之一。状态改变只能是pending到fulfilled或者pending到rejected。状态改变不可逆。

支持链式调用。

(1) 原型方法

Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}

(2) 静态方法

Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}
//...

Promise的优缺点

优点:

状态不可改变

链式调用解决回调地狱问题,让代码更清晰,更易维护。

缺点:

不能在执行中中止

在pending中不能查看异步到什么状态了。

promise源码实现

首先我们先看看我们怎么使用Promise的

new Promise(function (resolve, reject) {
setTimeout(() => {
// resolve("异步成功拉");
reject("异步失败啦");
}, 1000);
}).then(
function (data) {
console.log(data); /* then里面可以是同步的代码,也可以是异步的promise */
// return new Promise(function (resolve, reject){
// setTimeout(() => {
// resolve("第一个then里面的异步");
// }, 1000);
// }); return "链式调用第一个then的返回值";
},
function (reason) {
console.log("第一个then" + reason);
return "第一个then reject 后的 失败 reason"
}
)

实现一个简单的Promise构造函数

function Promise(executor) {
var _this = this;
this.data = undefined;//数据
this.status = "pending";//状态 this.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
this.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面 var resolve = function (data){
if (_this.status === "pending"){
_this.status = "resolved";
_this.data = data;
for(var i = 0; i < _this.onResolvedCallback.length; i++) {
_this.onResolvedCallback[i](data)
}
}
} var reject = function (errReason) {
if (_this.status === "pending"){
_this.status = "rejected";
_this.data = errReason;
for(var i = 0; i < _this.onRejectedCallback.length; i++) {
_this.onRejectedCallback[i](errReason)
}
}
}
try{
executor(resolve, reject);
} catch(e){
reject(e);
}
}

由上面的代码可以看出 executor一般来说应该是一个异步,等待其执行完后 成功或者失败,然后执行其回调resolve或者reject。 然后在resolve或者reject里面执行then里面注册的回调函数。所以then函数应该是一个注册用户回调 到 onResolvedCallback或者onRejectedCallback里的过程。

then的实现

在实现的then函数之前,我们来明确一下then函数要做那几件事情。

1、注册用户回调到 _this.onResolvedCallback 或者 _this.onRejectedCallback

2、支持链式调用, 其实就是then函数执行完后应该返回一个promise对象,并且根据promise A+标准,这个promise应该是一个新的promise。

3、处理三种状态, executor可能是一个同步的函数也有可能是一个异步的函数,所以在执行then的时候 _this.status 可能是pending(executor是异步的情况),_this.status 可能是resolve/reject (executor是同步的情况)

而status是pending的情况下是一个注册的过程,也就是将回调存起来,等待status变成resolve或者reject再执行回调。

而status是resolve/reject的情况下就直接执行对调了。

上面这段解释建议边看下面的代码边理解上面这段话


//onResolved onRejected 为调用者传进来的 成功和失败的回掉
Promise.prototype.then = function (onResolved, onRejected){ var _this = this
var promise2;
// 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {} //如果上面的executor是一个异步的,执行then的时候 status一定是pending
if (this.status === "pending"){
//生成一个新的promise
promise2 = new Promise(function(resolve, reject) {
//将调用者的回调包装后注册进promise的回调队列
_this.onResolvedCallback.push(function (value){
//这里的value是在onResolvedCallback里面的函数执行时传的
try {
var x = onResolved(_this.data)
if (x instanceof Promise) {
//then里面的回调如果是异步的promise,则等待异步执行完后,再进入promise2的then中注册的回调
x.then(resolve, reject);
}
else{
//如果是同步的,直接进入promise2的then中注册的回调
resolve(x);
}
} catch (e) {
reject(e)
}
}); _this.onRejectedCallback.push(function (reason) {
try {
var x = onRejected(_this.data)
if (x instanceof Promise) {
x.then(resolve, reject);
}
else{
reject(x);
}
} catch (e) {
reject(e)
}
});
})
return promise2;
} //如果executor是同步的, 则执行then的时候 status为 resolved或者rejected
if (_this.status === 'resolved') {
// 如果promise1(此处即为this/_this)的状态已经确定并且是resolved,我们调用onResolved
// 因为考虑到有可能throw,所以我们将其包在try/catch块里
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onResolved(_this.data)
if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
x.then(resolve, reject)
}
resolve(x) // 否则,以它的返回值做为promise2的结果
} catch (e) {
reject(e) // 如果出错,以捕获到的错误做为promise2的结果
}
})
} // 此处与前一个if块的逻辑几乎相同,区别在于所调用的是onRejected函数,就不再做过多解释
if (_this.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onRejected(_this.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
}
}

看完代码后我继续来解释promise2中的处理过程,现在需要想的是如何在 promise2中如何执行完后如何将正确的数据交给下一个then。

所以需要判断x(onResolved或者onRejected执行的结果)是一个什么值,如果是一个普通的值则直接调用promise2的resolve或者reject,但是如果x是一个promise对象,则我们需要等待这个promise对象状态变成reosolve或者reject,也就是等待这个promise处理完异步任务(需要用到promise,里面一般都是异步任务),所以调用x.then(resove,reject),这里是直接将promise2的resolve/reject作为回调的。也就是等待x这个promise对象执行完后,交给promise2的then里面的回调,衔接整个链式的过程。

catch的实现

Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected)
}

到此,promise的整个原理就算大部分完成了,其实理解起来并不是那么难,对不对。

确保x的处理,能与不同的promise进行交互

根据promise A+规范,上面在promise2中处理x并将处理值交给 promise2.then的回调的整个过程并没有考虑到''不符合promise规范的对象并带有then方法的情况'',promise A+规范希望能以最保险的方式将x传递到promise2.then,即使x是一个不遵循promise规范,但是带有then的对象也能够完美的处理。

所以需要对x更进一步的处理,然后将数据交给下一步

/即我们要把onResolved/onRejected的返回值,x,

当成一个可能是Promise的对象,也即标准里所说的thenable,

并以最保险的方式调用x上的then方法,如果大家都按照标准实现,

那么不同的Promise之间就可以交互了。而标准为了保险起见,

即使x返回了一个带有then属性但并不遵循Promise标准的对象
/

递归解决,只要x带有then方法,就会像剥洋葱一样层层的剥开,直到x是一个非类似promise的这种处理异步的对象,非thennable对象。

function resolvePromise(promise2, x, resolve, reject) {
var then
var thenCalledOrThrow = false if (promise2 === x) { // 对应标准2.3.1节
return reject(new TypeError('Chaining cycle detected for promise!'))
} if (x instanceof Promise) { // 对应标准2.3.2节
// 如果x的状态还没有确定,那么它是有可能被一个thenable决定最终状态和值的
// 所以这里需要做一下处理,而不能一概的以为它会被一个“正常”的值resolve
if (x.status === 'pending') {
x.then(function(value) {
resolvePromise(promise2, value, resolve, reject)
}, reject)
} else { // 但如果这个Promise的状态已经确定了,那么它肯定有一个“正常”的值,而不是一个thenable,所以这里直接取它的状态
x.then(resolve, reject)
}
return
} if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) { // 2.3.3
try { // 2.3.3.1 因为x.then有可能是一个getter,这种情况下多次读取就有可能产生副作用
// 即要判断它的类型,又要调用它,这就是两次读取
then = x.then
if (typeof then === 'function') { // 2.3.3.3
then.call(x, function rs(y) { // 2.3.3.3.1
if (thenCalledOrThrow) return // 2.3.3.3.3 即这三处谁选执行就以谁的结果为准
thenCalledOrThrow = true
return resolvePromise(promise2, y, resolve, reject) // 2.3.3.3.1
}, function rj(r) { // 2.3.3.3.2
if (thenCalledOrThrow) return // 2.3.3.3.3 即这三处谁选执行就以谁的结果为准
thenCalledOrThrow = true
return reject(r)
})
} else { // 2.3.3.4
resolve(x)
}
} catch (e) { // 2.3.3.2
if (thenCalledOrThrow) return // 2.3.3.3.3 即这三处谁选执行就以谁的结果为准
thenCalledOrThrow = true
return reject(e)
}
} else { // 2.3.4
resolve(x)
}
}

将promise2的处理过程改一下,三种情况都要改。

promise2 = new Promise(function(resolve, reject) {
//将调用者的回调包装后注册进promise的回调队列
_this.onResolvedCallback.push(function (value){
//这里的value是在onResolvedCallback里面的函数执行时传的
try {
var x = onResolved(value);
//解决调用者定义的onResolved的返回值 x 是非规范的Promise对象且带有then方法的情况
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}); _this.onRejectedCallback.push(function (reason) {
try {
var x = onRejected(reason)
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
});
})
return promise2;

测试一下

//测试不遵守promise的对象,且带有then方法
var notPromise = function () {
//
} notPromise.prototype.then = function (onResolved, onRejected) {
setTimeout(function () {
onResolved("不遵守promise规范的对象")
}, 1000)
} //测试
new Promise(function (resolve, rejected) {
setTimeout(function () {
resolve("异步开始了");
},1000)
}).then(function (data) {
console.log(data);
//下面的返回可以是 promise 也可以是普通的值, 还可以是不准寻promise规范的对象但带有then方法(在resolve都给与了支持)
//普通值和promise就不测试了。
//测试一下遵循promise的对象, 且带有then方法
return new notPromise();
}).then(function (data) {
//在then里面 会把上一个传递下来的值(new notPromise())不断的调它的then方法,知道确定没有then可以调用了,就递交到下一个then
console.log(data); // 这里的 data 不是 (new notPromise())而是它的then方法返回的。
})

值穿透问题

new Promise(resolve=>resolve(8))
.then()
.catch()
.then(function(value) {
alert(value)
})

跟下面这段代码的行为是一样的

new Promise(resolve=>resolve(8))
.then(function(value){
return value
})
.catch(function(reason){
throw reason
})
.then(function(value) {
alert(value)
})

不传回调,则使用默认回调,所以在默认回调上改改,让它将值传递下去

onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}

promise中止问题(面试题哦)

在一些场景下,我们可能会遇到一个较长的Promise链式调用,在某一步中出现的错误让我们完全没有必要去运行链式调用后面所有的代码,类似下面这样(此处略去了then/catch里的函数):

先给出结果

Promise.cancel = Promise.stop = function() {
return new Promise(function(){})
}
//下面我们来尝试一下,如果遇到错误,则不会执行alert(1)
new Promise(function(resolve, reject) {
resolve(42)
})
.then(function(value) {
var isErr = true; //尝试更改成 true 或者 false 看alert(1);是否执行
if (isErr){
// "Big ERROR!!!"
return Promise.stop()
} })
//值的穿透
.catch()
.then()
.then()
.catch()
.then(function () {
alert(1);
})

return new Promise(function(){})这段话就意味着x是一个promise对象, 则一定会走 x。then(resolve,reject) 交给promise2的then。但是这里new Promise(function(){}根本就没有resolve或者reject,所以它的状态一直为pending, 所以永远不会执行 x.then(resolve,reject)里面的resolve/reject,那么状态就传递不下去,链式就算是断开了。

promise链上没有catch等错误处理回调,怎么看到错误

没有错误处理函数,就给个默认的错误处理

function reject(reason) {
setTimeout(function() {
if (_this.status === 'pending') {
_this.status = 'rejected'
_this.data = reason
if (_this.onRejectedCallback.length === 0) {
console.error(reason)//默认的错误处理
}
for (var i = 0; i < _this.rejectedFn.length; i++) {
_this.rejectedFn[i](reason)
}
}
})
}

Promise静态方法的实现

列几个比较常用,很好理解,看代码基本就能明白,特别是Promise.all Promise.race的实现哦,面试常考原理。

Promise.all = function(promises) {
return new Promise(function(resolve, reject) {
var resolvedCounter = 0
var promiseNum = promises.length
var resolvedValues = new Array(promiseNum)
for (var i = 0; i < promiseNum; i++) {
(function(i) {
Promise.resolve(promises[i]).then(function(value) {
resolvedCounter++
resolvedValues[i] = value
if (resolvedCounter == promiseNum) {
return resolve(resolvedValues)
}
}, function(reason) {
return reject(reason)
})
})(i)
}
})
} Promise.race = function(promises) {
return new Promise(function(resolve, reject) {
for (var i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(function(value) {
return resolve(value)
}, function(reason) {
return reject(reason)
})
}
})
} Promise.resolve = function(value) {
var promise = new Promise(function(resolve, reject) {
resolvePromise(promise, value, resolve, reject)
})
return promise
} Promise.reject = function(reason) {
return new Promise(function(resolve, reject) {
reject(reason)
})
}

然后给一个完整版的代码

try {
module.exports = Promise
} catch (e) {} function Promise(executor) {
var self = this self.status = 'pending'
self.onResolvedCallback = []
self.onRejectedCallback = [] function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(function() { // 异步执行所有的回调函数
if (self.status === 'pending') {
self.status = 'resolved'
self.data = value
for (var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value)
}
}
})
} function reject(reason) {
setTimeout(function() { // 异步执行所有的回调函数
if (self.status === 'pending') {
self.status = 'rejected'
self.data = reason
for (var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason)
}
}
})
} try {
executor(resolve, reject)
} catch (reason) {
reject(reason)
}
} function resolvePromise(promise2, x, resolve, reject) {
var then
var thenCalledOrThrow = false if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'))
} if (x instanceof Promise) {
if (x.status === 'pending') { //because x could resolved by a Promise Object
x.then(function(v) {
resolvePromise(promise2, v, resolve, reject)
}, reject)
} else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
x.then(resolve, reject)
}
return
} if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then //because x.then could be a getter
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise2, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
resolve(x)
}
} Promise.prototype.then = function(onResolved, onRejected) {
var self = this
var promise2
onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
return v
}
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
throw r
} if (self.status === 'resolved') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() { // 异步执行onResolved
try {
var x = onResolved(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
} if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() { // 异步执行onRejected
try {
var x = onRejected(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
} if (self.status === 'pending') {
// 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
return promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(value)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
}) self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(reason)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
})
}
} Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
} Promise.deferred = Promise.defer = function() {
var dfd = {}
dfd.promise = new Promise(function(resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}

其他静态方法代码参考

https://github.com/ab164287643/Promise3/blob/master/Promise3.js

本文参考

https://github.com/xieranmaya/blog/issues/3