跑了非常多结果错误的测试,终于让我这份promise通过了测试,感天动地! 天知道我看这个报错看了多少遍了。
前言
首先是关于PromiseA+规范,可以看我之前的这篇文章,这里就不赘述关于PromsieA+规范了。
然后在手写之前,我们应该清楚的认识到我们要实现什么内容,下面是promise常用的函数,也是我们要一个一个去实现的。
Promise.resolve(value) --类方法,返回的状态为resolved
Promise.reject(reason) --类方法,返回的状态为rejected
Promise.prototype.then --实例方法,注册回调函数
Promise.prototype.catch --实例方法,捕获异常
Promise.race -- 类方法,返回最先执行的promise任务结果
Promise.all -- 类方法,返回所有任务成功的结果或者其中一个任务失败的结果
Promise构造函数
接着我们开始一步一步来,首先是构造函数。 我们先先定义三个常量表示promise的三个状态。因为后面会经常用到,索性先声明,方便使用。
然后创建我们自定义的函数Promise,传入的参数是执行函数。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function Promise(excutor) {
}
先声明我们需要用到的,比如初始化value和reason,以及添加两个数组存储成功和失败的回调。
function Promise(excutor) {
// 将当前promise对象保存起来,以便resolve和reject里面访问
const that = this;
// 给当前promise对象指定初始状态,初始状态为pending
this.status = PENDING;
this.value = null; // 初始化value
this.reason = null; // 初始化reason
// 构造函数里面添加数组存储成功和失败的回调
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
}
接着在在函数里面写我们需要的resolve和reject方法。
function Promise(excutor) {
-------省略上述代码---------
// resolve方法参数是value
function resolve(value) {
// 如果当前状态不是pending,直接结束
if (that.status !== PENDING) {
return
}
// 如果当前状态是pending,进行下一步
// 将状态改成fulfilled
that.status = FULFILLED;
// 保存value数据
that.value = value;
// 如果有待执行callback函数,立即执行回调函数
if (that.onFulfilledCallbacks.length > 0) {
// 遍历数组将所有成功的回调拿出来执行
that.onFulfilledCallbacks.forEach(callback => {
callback(that.value);
});
}
}
// reject方法参数是reason
function reject(reason) {
// 如果当前状态不是pending,直接结束
if (that.status !== PENDING) {
return
}
// 将状态改成rejected
that.status = REJECTED;
// 保存reason数据
that.reason = reason;
// 判断是否有待执行函数
if (that.onRejectedCallbacks.length > 0) {
// resolve里面将所有失败的回调拿出来执行
that.onRejectedCallbacks.forEach(callback => {
callback(that.reason);
});
}
}
}
实现了resolve和reject之后执行传入的函数
//立即同步执行excutor
try {
excutor(resolve, reject);
} catch (error) { // 如果执行器抛出异常,那promise对象变为rejected状态
reject(error);
}
到这里基本上就实现了遵循PromiseA+规范的promise构造函数。完整的代码会在文末给出。
Promise解决过程
根据PromiseA+规范来看,我们还需要写一个Promise解决过程。
Promise 解决过程 是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。
function resolvePromise(promise, x, resolve, reject) {
// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
// 这是为了防止死循环
if (promise === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
if (x instanceof Promise) {
// 如果 x 为 Promise ,则使 promise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
// 这个if跟下面判断then然后拿到执行其实重复了,可有可无
x.then(function(y) {
resolvePromise(promise, y, resolve, reject);
}, reject);
}
// 如果 x 为对象或者函数
else if (typeof x === 'object' || typeof x === 'function') {
if (x === null) {
return resolve(x);
}
try {
// 把 x.then 赋值给 then
var then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果 then 是函数
if (typeof then === 'function') {
var called = false;
try {
then.call(
x,
function(y) {
// 如果 resolvePromise 和 rejectPromise 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量called
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
function(r) {
if (called) return;
called = true;
reject(r);
});
} catch (error) {
if (called) return;
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
这里只需要根据规范一步一步实现即可。此处参考了这篇文章。
Promise原型对象的then()
Promise的then方法必须返回一个promise对象
promise2 = promise1.then(onFulfilled, onRejected);
1、如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
2、如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
3、如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
4、如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
首先我们创建一个then方法,挂载在原型链上,因为then方法是Promise原型对象的方法。
另外,Promise的状态有三种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected), 所以我们分情况来处理。
Promise.prototype.then = function(onFulfilled, onRejected) {
const that = this; // 保存一下this
if (this.status === FULFILLED) {
}
if (this.status === REJECTED) {
}
if (this.status === PENDING) {
}
}
如果当前是FULFILLED状态,异步执行onResovle并改变return的promise状态;
如果当前是REJECTED状态,异步执行onReject并改变return的promise状态;
如果还是PENDING状态,我们就将回调保存下来。
接下来我就写一个fulfilled状态来作为例子,注意最终返回一个新的Promise对象。
if (this.status === FULFILLED) {
var promise2 = new Promise((resolve, reject) => {
setTimeout(function() {
try {
// 如果抛出异常,return的promise就会失败,reason就是error
// 如果回调函数执行返回非promise,return的promise就会成功,value就是返回的值
// 如果回调函数执行返回是promise,return的promise的结果就是这个promise的结果
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
})
return promise2; //返回一个新的promise对象
}
而状态是rejected的时候也是同理,当状态是pending的时候其实就是合并一下前面两个状态。做到这里的时候then方法基本上也完成了。
但是要注意,我们缺少了判断传入的参数是不是函数。因为有可能传的是数字或者其他类型的值。所以我们需要在前面加一个判断。
var realOnFulfilled = onFulfilled;
if (typeof realOnFulfilled !== 'function') {
realOnFulfilled = function(value) {
return value;
}
}
var realOnRejected = onRejected;
if (typeof realOnRejected !== 'function') {
realOnRejected = function(reason) {
throw reason;
}
}
Promise原型对象的catch()
catch()就是指定失败的回调函数,然后返回一个新的promise对象。 同样的,也是挂载在原型链上,然后直接通过我们刚刚写的then方法,返回新的promise对象。
Promise.prototype.catch = function(onRejected) {
return this.then(undefined, onRejected)
}
Promise函数对象的resolve方法
resolve方法就是返回一个指定结果的成功的promise。并且,当传入的是promise对象时,这个对象就是promise的结果。反之,如果是传数字之类的,直接把promsie状态变成成功处理的状态,数据就是传入的值。
Promise.resolve = function(value) {
// 返回一个成功/失败的promise
return new Promise((resolve, reject) => {
// 判断value是否为promise
if (value instanceof Promise) { // value是promise => 使用value的结果作为promise的结果
value.then(resolve, reject)
} else { // value不是promise => promise变为成功,数据是value
resolve(value)
}
})
}
Promise函数对象的reject方法
reject方法返回一个指定reason的失败的promise。
写过了上面的resolve方法,我们就可以很容易的写出reject方法了。
Promise.reject = function(reason) {
// 返回一个失败的promise
return new Promise((resolve, reject) => {
reject(reason)
})
}
Promise函数对象的all方法
all方法就是返回一个promise,并且,只有当所有promise都成功时才成功,否则失败。
Promise.all = function(promises) {
// 用来保存所有成功value的数组
const values = new Array(promises.length)
// 用来保存成功promise的数量
let resolvedCount = 0 // 计数器
return new Promise((resolve, reject) => {
if (promises.length === 0) {
return resolve(values)
}
// 遍历promises获取每个promise的结果
promises.forEach((p, index) => {
p.then(
value => {
resolvedCount++ //成功的数量+1
// p成功,将成功的value保存到values
// values.push(value)
values[index] = value
// 如果全部成功了,将return的promise改变成功
if (resolvedCount === promises.length) {
resolve(values)
}
},
reason => { // 只要有一个失败了,return的promise就失败
reject(reason)
}
)
})
})
}
这里的难点就是,需要用到一个计数器,也就是一个变量来保存所有成功的promsie的数量,每次promise成功时计数器加一,当成功的promise的数量跟传入的promise数组的数量相同,也就是代表所有的promise都成功了。
Promise函数对象的race方法
race方法返回一个promise,其结果由第一个完成的promise结果决定。 由第一个完成的promise结果决定就比较简单了,我们只需要遍历传进来的数组,一旦有成功的,promise的状态就可以修改成成功,一旦有失败的,promise的状态就可以修改为失败。
Promise.race = function(promises) {
// 返回一个promise
return new Promise((resolve, reject) => {
if (promises.length === 0) {
return resolve()
}
// 遍历promises获取每个promise的结果
promises.forEach((p, index) => {
Promise.resolve(p).then(
value => { // 一旦有成功了,将return的promise状态变为成功
resolve(value)
},
reason => { // 只要有一个失败了,return的promise就失败
reject(reason)
}
)
})
})
}
promise测试方法以及过程
做到这里其实我们已经基本上是完成了常用的方法。可以先来测试一波。 这里我用的是promiseA+规范官方测试工具promises-aplus-tests。 根据里面的文档,我们需要先定义一个静态方法deferred,
Promise.deferred = function() {
var result = {};
result.promise = new Promise(function(resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
然后我们把这个包下载下来,直接npm下载即可。
npm install promises-aplus-tests -g
下载完之后可以看到有个文件夹,
然后再配置一下里面的package.json
{
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
},
"scripts": {
"test": "promises-aplus-tests Promise"
}
}
这样我们就可以进行测试了,在终端上运行以下命令:
promises-aplus-tests Promise.js
promise测试结果
如果写的promise没有问题的话,可以看到下面的结果,通过了官方的872个测试用例。
结语
本文的完整代码附上:github.com/nankou/stud… 当然,我这份手写的promise绝对不是最好最便捷最准确的,如果大家有发现什么错误,欢迎大家批评指正。希望大家一起进步!