说起前端利器之Promise在座各位应该很熟悉了,但是你真的知道各种.then
.catch
交叉链式调用后的结果么,比如简单的Promise.reject(1).catch(e => e).catch(e => console.log(e))
的打印结果是什么?你又可曾又想过其中是怎么实现的。下面我们一步一步来试着写一个吧!(结果是没有打印)
简单实现一点
也许你并不知道完整的规范(如Promises/A+)需要实现哪些东西。但我们可以从我们平时使用的简单功能来。下方代码截取自阮一峰的博客,链接
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
一个Promise实例有自己内部的状态,从pending
到fulfilled
或rejected
,并提供两个方法接受值改变状态
// 定义三种状态
var Status = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
};
function MyPromise(fn) {
var self = this;
self.status = Status.PENDING;
self.value = null;
self.error = null;
// 改变状态
function resolve(value) {
// 一旦状态改变无法变更
if (self.status === Status.PENDING) {
self.value = value;
self.status = Status.FULFILLED;
}
}
function reject(error) {
if (self.status === Status.PENDING) {
self.error = error;
self.status = Status.REJECTED;
}
}
// 执行新建Promise时传入的方法
fn(resolve, reject);
}
为了实现如下的调用方式,需要在Promise上新加一个then
方法
promise.then(function(value) {
// success
}, function(error) {
// failure
});
让我们把精力放到后面更关键和复杂的场景下,这里就不贴全部代码了,相信在座各位都能够实现。也可自己试着尝试
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// TODO
// 将onFulfilled和onRejected存到Promise的实例里,等到resove、reject调用的时候触发
}
.then链式调用
熟练使用Promise的小伙伴都知道它可以链式调用用以解决串行的请求之类的场景。
// getJSON是一个请求JSON数据的方法,返回一个Promise
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
那么问题来了,.then(xx函数).then
中第一个.then
返回了个啥,为啥后面还可以再.then
。回答这个问题可以很简单,返回了另一个Promise,也可以很复杂。大家都能看出第一个.then
里传入了一个函数,这个函数又return
了一个值。于是后面那个then
得到的参数就是上一个then
里函数返回的值。so far so easy,不过前提是这个返回的值不是一个类Promise对象(先就理解为Promise吧)。
如果上面这个函数返回了一个Promise对象,那么第二个then
里面可就不是前面的返回值了,这么说大家其实也知道
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL); // 这里return的是另一个Promise
}).then(function (comments) { // 这里的参数并不是前面返回的Promise,而是Promise的value
console.log("resolved: ", comments);
}, function (err){
console.log("rejected: ", err);
});
到这里其实也引出了一个关键操作,我们把处理下方onFulfilled
的return值的操作抽象为一个过程,就取个名字叫Promise解析过程(Promise Resolution Procedure)吧。这里搬一个有点难看的表达式出来吧[[Resolve]](promise, x)
// 这里promise2对应上面例子中的.then(xx函数)的返回值,
promise2 = promise1.then(onFulfilled, onRejected);
如果onFulfilled或者onRejected返回了一个x,那么就执行[[Resolve]](promise2, x)
。那么这个过程具体干了啥呢?规范里定义的场景就很多了,这里选一些主要的来说下吧。
- 如果
x
是一个Promise,那么x
的状态改变会同步改变promise2的状态。上面的例子中.then(xx函数)返回的函数就是promise2,他的状态的值都取决于xx函数
返回的那个Promise。 - 如果
x
不是一个Promise,且它没有.then
方法(一般场景没有这种使用场景)。那么直接resolve掉promise2,resolve(x)
。
好了我们先不实现这个过程,我们先来调用它来实现链式调用。前面说了.then(xx函数)
返回了一个Promise,我们可以叫这个为桥梁Promise,起了一个连接作用。
// 先定义[[Resolve]](promise, x)函数的签名
function resolvePromise(promise, x, resolve, reject) {}
因为这个过程需要改变这个promise的状态,所以还需要传入resolve和reject方法
MyPromise.prototype.then = function(onFulfilled, onRejected) {
var self = this;
var bridgeAdapter; // 只是为了保存resolve方法
var bridgePromise = new MyPromise(function(resolve, reject) {
bridgeAdapter = {
resolve,
reject
}
});
if (self.status === Status.PENDING) {
// 这个数组会在状态变更的时候被调用
self.onFulfilledCallbacks.push(value => {
const x = onFulfilled(value);
// 刚定义的过程方法
resolvePromise(bridgePromise, x, bridgeAdapter.resolve, bridgeAdapter.reject);
});
self.onRejectedCallbacks.push(err => {
const x = onRejected(err);
resolvePromise(bridgePromise, x, bridgeAdapter.resolve, bridgeAdapter.reject);
});
return bridgePromise;
}
// TODO,状态早已变更过的情况
return bridgePromise;
}
这样一样我们就把这个桥梁Promise交给了下一个.then
。下面就是如何去改变这个桥梁Promise的状态,也就是[[Resolve]](promise, x)
要怎么实现。其实主要过程很简单,去除各种判断后:
function resolvePromise(promise, x, resolve, reject) {
var then = x ? x.then : null;
if (
typeof then === 'function'
) {
// 就是x.then()
then.call(x, function(value) {
resolvePromise(promise, value, resolve, reject);
}, function(error) {
reject(error);
})
}
else {
resolve(x);
}
}
这里面主要是一个递归,对于返回的x
这个Promise,我们需要用then
获取它的结果,这里又有一个新的逻辑。
- 如果
x
这个Promise被resolve的值是y,继续执行[[Resolve]](promise, y)
,这个promise还是那个桥梁bridge。
这个情况就真的很神奇了,举个例子
const promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise
.then(function(value) {
return new Promise(function(resolve, reject) {
resolve(
new Promise(function (resolve, reject) {
resolve(3);
})
)
}) // 要是这里再.then一下会发生啥?
})
.then(function(value) {
console.log('value: ', value);
})
试想一下会打印出来什么?答案可以发到评论里
综上所述
我们有一个Promise的构造函数,里面维护自己的状态,和提供改变状态的方法,并且保存.then
注册的回调。
function MyPromise(fn) {
var self = this;
self.status = Status.PENDING;
self.value = null;
self.error = null;
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value) { // 省略 }
function reject(error) { // 省略 }
fn(resolve, reject);
}
然后有一个解析Promise的方法,根据传入的值x
改变Promise的状态
function resolvePromise(promise, x, resolve, reject) { // 省略 }
然后就是Promise的.then
方法,会生成一个新的桥梁Promise,并把它的生杀大权交给onFulfilled的返回值(resolvePromise)
MyPromise.prototype.then = function(onFulfilled, onRejected) {
var self = this;
var bridgeAdapter;
var bridgePromise = new MyPromise(function(resolve, reject) {
bridgeAdapter = {
resolve,
reject
}
});
// 省略
self.onFulfilledCallbacks.push(value => {
const x = onFulfilled(value);
resolvePromise(bridgePromise, x, bridgeAdapter.resolve, bridgeAdapter.reject);
});
return bridgePromise;
}
以上就是整个Promise核心的结构和功能模块,虽然做了很多简化处理,不过最终代码也 不超过百行,为了达到Promises/A+标准还需要补充很多场景的处理才行,那么就请听下回分解了。喜欢的话请点赞、收藏、转发一波~
本文首发于掘金,转载请注明出处
参考资料
- promisesaplus.com/ 官方规范
- mp.weixin.qq.com/s/JMnJUCVLM… 从零开始写一个符合Promises/A+规范的promise