Promise是什么?
在Javascript的世界中,代码都是单线程执行的。这就导致我们的代码里会有嵌套回调函数,一旦嵌套过多,导致代码不容易理解和维护。
为了降低异步编程的复杂性,开发人员一直在寻找各种解决方案,Promise只是用来处理异步操作的其中一个方案。
下面我就结合着Promise的使用场景,来逐一手写Promise的实现原理。
因为ES6最终采用了Promise/A+ 规范,下面所有代码都是基于Promise/A+规范来实现。有兴趣的同学可以查看 Promise/A+规范 https://promisesaplus.com/
Promise的基本使用:
let promise = new Promise((resolve,reject)=>{resolve('success'); //这里如果是reject('fail')
});
promise.then((res)=>{console.log(res); // 输出:success
},(err)=>{console.log(err); // 上面如果执行reject('fail') 这里就输出:fail
});
复制代码
看图就很好理解了,resolve找then里的成功回调,reject找then里失败的回调。
那么如何用我们自己的方式一步步实现这样的一个Promise类呢?直接上代码吧,我会注释得稍微详细点。
class Promise { //创建一个Promise类constructor(executor) {this.status = 'pending'; //初始默认状态为pendingthis.value = undefined; //默认赋值为undefinedthis.reason = undefined; //默认赋值为undefinedlet resolve = (value) => {if (this.status === 'pending') { //只有状态为pending才能转换状态this.value = value; //将传递进来的的值赋给value保存this.status = 'resolved'; //将状态设置成resolved}}let reject = (reason) => {if (this.status === 'pending') { //只有状态为pending才能转换状态this.reason = reason; //将传递进来的失败原因赋给reason保存this.status = 'rejected'; //将状态设置成rejected}}executor(resolve, reject); //默认执行executor}then(onFulfilled, onRejected) { //等同于es5的Promise.prototype.then 当调用then的时候,根据状态,来执行不同的函数if (this.status === 'resolved') { //如果状态是resolvedonFulfilled(this.value); //执行成功的resolve,并将成功后的值传递过去}if (this.status === 'rejected') { //如果状态是rejectedonRejected(this.reason); //执行失败的reject,并将失败原因传递过去}}
}
module.exports = Promise; //将Promise导出复制代码
现在只要在js代码中引入Promise库,就能实现最基本的Promise功能了。
输出结果:success
还有一点需要注意,就是当代码出现错误的情况下,我们需要能够捕获错误,并且处理错误。所以在executor(resolve, reject)这一行代码需要处理下,包上一层try/catch,如下:
try {executor(resolve, reject);
} catch (e) {reject(e); //如果发生错误,将错误放入reject中
}
复制代码
setTimeout调用问题
此时的Promise类可以实现调用resolve,也可以调用reject。调用then之后都会执行相应的函数。看似已经没有问题了。但如果将resolve放在一个定时器里呢?看看下面的代码:
let promise = new Promise((resolve, reject) => {setTimeout(() => {resolve('setTimeout');}, 500);
})
promise.then((res) => {console.log(res);
}, (err) => {console.log(err);
})
复制代码
我们会发现上面的代码在控制台完全没有反应,这是为什么呢?实际上是因为我们压根就没有处理这种延迟调用的情况。现在我们来处理以下:
class Promise { //创建一个Promise类constructor(executor) {...此处略去部分代码this.successStore = []; //定义一个存放成功函数的数组this.failStore = []; //定义一个存放失败函数的数组let resolve = (value) => {if (this.status === 'pending') { //只有状态为pending才能转换状态...此处略去部分代码this.successStore.forEach(fnc => fnc()); //一次执行数组中的成功函数}}let reject = (reason) => {if (this.status === 'pending') { //只有状态为pending才能转换状态...此处略去部分代码this.failStore.forEach(fnc => fnc()) //依次执行数组中的失败函数}}...此处略去部分代码}then(onFulfilled, onRejected) { //等同于es5的Promise.prototype.then 当调用then的时候,根据状态,来执行不同的函数...此处略去部分代码if (this.status === 'pending') { //此处增加一种状态判断this.successStore.push(() => { //当状态为pending时将成功的函数存放到数组里onFulfilled(this.value);})this.failStore.push(() => { //当状态为pending时将失败的函数存放到数组中onRejected(this.reason);})}}
}
module.exports = Promise; //将Promise导出复制代码
这样就解决了setTimeout调用的问题了。
then的链式调用
Promise/A+规范里说了,then()方法返回的必须是Promise实例,这里我们可以理解成返回的必须是一个新的Promise实例。所以then()方法后面可以继续跟另一个then()方法进行链式调用。
但是then()方法里可能发挥一个值或者返回的是一个Promise实例,这就需要我们分别处理这两种情况。
第一种:
let promise = new Promise((resolve, reject) => {resolve('success');
})
p.then((res)=>{console.log(res); // successreturn 'hello world!'
},(err)=>{console.log(err);
}).then((res)=>{console.log(res); // hello world
},(err)=>{console.log(err);
})
复制代码
在then()方法的回调里返回一个普通值,无论是成功还是失败的回调,都会进入到下一个then()的成功态里。如下:
let promise = new Promise((resolve, reject) => {reject('fail');
})
p.then((res)=>{console.log(res);
},(err)=>{console.log(err); //failreturn 'fail';
}).then((res)=>{console.log(res); //fail 注意:此时走的是成功回调,并非失败的回调
},(err)=>{console.log(err);
})
复制代码
第二种:
let promise = new Promise((resolve, reject) => {resolve();
})
promise.then((res)=>{return new Promise((resolve,reject)=>{ //返回一个新的Promiseresolve('hello world'); })
},(err)=>{console.log(err);
}).then((res)=>{console.log(res); //hello world
},(err)=>{console.log(err);
})
复制代码
这两种情况都要考虑到,我们修改下代码:
function handlePromise(promise2, x, resolve, reject) {if (promise2 === x) { //promise2是否等于x,也就是判断是否将自己本身返回return reject(new TypeError('circular reference')); //如果是抛出错误}//判断x不是bull且x是对象或者函数if (x !== null && (typeof x === 'object' || typeof x === 'function')) {let called; //called控制resolve或reject 只执行一次,多次调用没有任何作用。try {let then = x.then; //取x.then()方法if (typeof then === 'function') { //如果是函数,就认为它是返回新的promisethen.call(x, y => { //如果y是promise继续递归解析if (called) return;called = true;handlePromise(promise2, y, resolve, reject); //递归解析promise}, r => {if (called) return;called = true;reject(r)})} else { //不是函数,就是普通对象resolve(x); //直接将对象返回}} catch (e) {if (called) return;called = true;reject(e);}} else { //x是普通值,直接走then的成功回调resolve(x);}
}
class Promise {constructor(executor) {this.status = 'pending';this.value = undefined;this.reason = undefined;this.successStore = [];this.failStore = [];let resolve = (value) => {if (this.status === 'pending') {this.value = value;this.status = 'resolved';this.successStore.forEach(fn => fn());}}let reject = (reason) => {if (this.status === 'pending') {this.reason = reason;this.status = 'rejected';this.failStore.forEach(fn => fn());}}try {executor(resolve, reject);} catch (e) {reject(e);}}then(onFulfilled, onRejected) { //原型上的方法let promise2; // 返回的新的promiseif (this.status === 'resolved') {promise2 = new Promise((resolve, reject) => {try {let x = onFulfilled(this.value);handlePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}})}if (this.status === 'rejected') {promise2 = new Promise((resolve, reject) => {try {let x = onRejected(this.reason); //x存放返回的结果handlePromise(promise2, x, resolve, reject); //处理返回结果的函数,已经在上面定义} catch (e) {reject(e);//报错执行reject}})}if (this.status === 'pending') {promise2 = new Promise((resolve, reject) => {this.successStore.push(() => {try {let x = onFulfilled(this.value); //x存放返回的结果handlePromise(promise2, x, resolve, reject);//处理返回结果的函数,已经在上面定义} catch (e) {reject(e); //报错执行reject}})this.failStore.push(() => {try {let x = onRejected(this.reason);//x存放返回的结果handlePromise(promise2, x, resolve, reject);//处理返回结果的函数,已经在上面定义} catch (e) {reject(e);//报错执行reject}})})}return promise2; //返回新的promise}
}
module.exports = Promise;复制代码
处理值穿透
先看实例
let promise = new Promise((resolve, reject)=>{resolve('hello world');
})
promise.then().then().then((res)=>{ console.log(res);//我们希望可以正常打印出hello world,如何处理呢?
})
复制代码
只需要在then的原型方法上加上一层判断,判断then里是否传递里函数,如果没有传递,我们手动传递一个函数,并让值返回。
then(onFulfilled, onRejected) { //原型上的方法onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : y => y; //判断是否是一个函数onRejected = typeof onRejected === 'function' ? onRejected : errr => { //判断是否是一个函数throw err; //注意,这里不是返回值,而是抛出错误}
}
复制代码
这样当我们调用Promise时就可以正常打印出hello world了。
添加catch方法
下面我们继续添加catch方法
then(onFulfilled, onRejected){...
}
catch(onRejected){ //在此处添加原型上的方法catchreturn this.then(null,onRejected);
}
复制代码
catch()其实就是简易版的then()方法,它没有成功的回调,只有失败的回调。所以按照上面的方式处理下就可以了。
添加Promise.all方法
Promise.all = function (promiseArrs) { //在Promise类上添加一个all方法,接受一个传进来的promise数组return new Promise((resolve, reject) => { //返回一个新的Promiselet arr = []; //定义一个空数组存放结果let i = 0;function handleData(index, data) { //处理数据函数arr[index] = data;i++;if (i === promiseArrs.length) { //当i等于传递的数组的长度时 resolve(arr); //执行resolve,并将结果放入}}for (let i = 0; i < promiseArrs.length; i++) { //循环遍历数组promiseArrs[i].then((data) => {handleData(i, data); //将结果和索引传入handleData函数}, reject)}})
}
复制代码
下面我们逐个添加reace、resolve和reject方法。
添加Promise.race方法
race比赛竞赛的意思,也就是谁跑的快就返回谁,不管你是成功还是失败。跟all方法比较相似,但更简单一些。循坏之后直接
Promise.race = function (promises) {return new Promise((resolve, reject) => {for (let i = 0; i < promises.length; i++) {promises[i].then(resolve, reject);}})
}
复制代码
经过上面的代码,以下两个方法就显得很简单了,直接返回一个新的Promise,然后执行成功/失败既可。
添加Promise.resolve方法
Promise.resolve = function (val) {return new Promise((resolve, reject) => resolve(val));
}
复制代码
添加Promise.reject方法
Promise.reject = function (val) {return new Promise((resolve, reject) => reject(val));
}
复制代码
如果想要用promises-aplus-tests测试是否符合Promise/A+规范的话,就要加上一段代码:
Promise.deferred = Promise.defer = function () { //这是promise的语法糖let dfd = {};dfd.promise = new Promise((resolve,reject)=>{dfd.resolve = resolve;dfd.reject = reject;})return dfd;
}
复制代码
在终端安装完promises-aplus-tests后,在文件的终端下执行promises-aplus-tests "文件名"既可
npm install promises-aplus-tests -g
promises-aplus-tests filename
复制代码
最后,解决下异步的问题,完整代码如下:
function handlePromise(promise2, x, resolve, reject) {if (promise2 === x) {return reject(new TypeError('circular reference'));}if (x !== null && (typeof x === 'object' || typeof x === 'function')) {let called;try {let then = x.then;if (typeof then === 'function') {then.call(x, y => {if (called) return;called = true;handlePromise(promise2, y, resolve, reject);}, r => {if (called) return;called = true;reject(r)})} else {resolve(x);}} catch (e) {if (called) return;called = true;reject(e);}} else {resolve(x);}
}
class Promise {constructor(executor) {this.status = 'pending';this.value = undefined;this.reason = undefined;this.successStore = [];this.failStore = [];let resolve = (value) => {if (this.status === 'pending') {this.value = value;this.status = 'resolved';this.successStore.forEach(fn => fn());}}let reject = (reason) => {if (this.status === 'pending') {this.reason = reason;this.status = 'rejected';this.failStore.forEach(fn => fn());}}try {executor(resolve, reject);} catch (e) {reject(e);}}then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : y => y;onRejected = typeof onRejected === 'function' ? onRejected : errr => {throw err;}let promise2; if (this.status === 'resolved') {promise2 = new Promise((resolve, reject) => {setTimeout(() => { //异步处理try {let x = onFulfilled(this.value);handlePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);})}if (this.status === 'rejected') {promise2 = new Promise((resolve, reject) => {setTimeout(() => { //异步处理try {let x = onRejected(this.reason);handlePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);})}if (this.status === 'pending') {promise2 = new Promise((resolve, reject) => {this.successStore.push(() => {setTimeout(() => { //异步处理try {let x = onFulfilled(this.value);handlePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);})this.failStore.push(() => {setTimeout(() => { //异步处理try {let x = onRejected(this.reason);handlePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);})})}return promise2;}
}Promise.all = function (promiseArrs) {return new Promise((resolve, reject) => {let arr = [];let i = 0;function processData(index, data) {arr[index] = data;i++;if (i === promiseArrs.length) {resolve(arr);}}for (let i = 0; i < promiseArrs.length; i++) {promiseArrs[i].then((data) => {processData(i, data);}, reject)}})
}Promise.deferred = Promise.defer = function () {let dfd = {};dfd.promise = new Promise((resolve, reject) => {dfd.resolve = resolve;dfd.reject = reject;})return dfd;
}module.exports = Promise;
复制代码
现在,一个符合Promise/A+规范的Promise类已经完成了。见解有限,如有描述不准确之处,请帮忙及时指出。如有错误,一定会及时更正。
参考
segmentfault.com/a/119000000…
promisesaplus.com/
zhuanlan.zhihu.com/p/25178630