背景
- 如看本文吃力的同学建议看一下前几篇的promise使用和例子
迅速理清promise运行机制与实际中的应用
利用Promise解决批量文件串行下载问题
不用promise.all只用promise做并行下载文件
手写Promise前必须知道的几个小例子【一】
手写Promise前必须知道的几个小例子【二】
一、实现同步功能
- 首先需要写出大致框架和同步功能。
- 代码如下:
function myPromise(executer){
this.state='pending';
this.value=undefined;
this.reason=undefined;
let that = this;
function resolve(value){
that.state='resolved'
that.value=value;
};
function reject(reason){
that.state='rejected'
that.reason = reason;
}
try{
executer(resolve,reject)
}catch(e){
reject(e)
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
if(this.state==='pending'){
}
if(this.state==='resolved'){
onFullfilled(this.value)
}
if(this.state==='rejected'){
onRejected(this.reason)
}
}
let f= new myPromise(function(resolve,reject){
resolve('value')
}).then(res=>console.log(res))
代码解读
- 先使用es5写法,便于大家理解,最后放es6写法。
- 首先Promise是个函数,它需要传一个function,就是excuter,就相当于我们使用Promise时那个new Promise(function(resolve,reject))中的function()。
- 自然这个function有2个参数,一个是resolve,一个是reject,所以需要在这个函数里写一个resolve和reject函数,供excuter执行。代码中写了resolve(‘value’)所以excuter执行后会执行函数里的resolve()。
- promise有三种状态,这个不用说了,所以写初始状态pending,这里得用this,才能继承给new。
- 传值还需要给resolve初始值和reject初始值undefined。
- 在resolve执行,给state换成相应状态,值换成相应值。
- 为什么要绑个that?因为resolve是excuter执行,也就是function(resolve,reject)函数体内的this,this是global,this会丢失,取不到内部状态。
- 再看看then:
- 由于new promise后需要使用.then,所以需要在promise.prototype上增加then方法。有人问为啥不加到Function.prototype上?因为new出来的是对象而不是方法,对象沿着原型链向上查询方法不会经过Function,如果加Function上,不New的话只用Promise倒是可以直接.then。
- 这个.then前面是挂在promise.prototype下的,这里this就是promise内部。所以列出三种状态和三种情况,pending情况暂时不讨论,resolve和reject是一样的,状态是resolve那么就执行then的onFullfilled,也就是.then当中的第一个参数res=>console.log(res)。这个res就是onFullfilled(this.value)当中的this.value。
- 为什么要写个try和catch?因为执行new myPromise(executer)的时候也可能报错这时候就返回reject就行了。有人可能问,.then中干嘛不加trycatch?因为.then中如果报错那么就需要下一个.then才能捕获异常,而不是应该本次本来进onFullfilled然后跳onRejected。
二、实现异步功能
- 刚才这个只能对同步执行的有效,那么如何实现异步也有效呢?
- 这个就跟前面几篇文章的小例子一个意思,先把将要执行的方法存到数组里,然后在异步完成后执行数组里存的函数。(不使用数组执行待更新)
function myPromise(executer){
this.state='pending';
this.value=undefined;
this.reason=undefined;
let that = this;
this.resolveArr=[];
this.rejectArr =[];
function resolve(value){
if(that.state==='pending'){
that.state='resolved'
that.value=value;
that.resolveArr.forEach(fn=>fn())
}
};
function reject(reason){
if(that.state==='pending'){
that.state='rejected'
that.reason = reason;
that.rejectArr.forEach(fn=>fn())
}
}
try{
executer(resolve,reject)
}catch(e){
reject(e)
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
if(this.state==='pending'){
this.resolveArr.push(()=>onFullfilled(this.value))
this.rejectArr.push(()=>onRejected(this.reason))
}
if(this.state==='resolved'){
onFullfilled(this.value)
}
if(this.state==='rejected'){
onRejected(this.reason)
}
}
let f= new myPromise(function(resolve,reject){
setTimeout(() => {
resolve('value')
}, 1000);
}).then(res=>console.log(res))
let p= new myPromise(function(resolve,reject){
resolve('ddd')
}).then((res)=>console.log(res))
代码解读
- 这地方难点在执行顺序上,我们先梳理下执行顺序。如果是异步操作,异步操作完成后会调用resolve函数,也就是promise里的resolve执行会在异步完成后直接调用。我们在.then后增加的onFullfilled函数,也就是(res)=>console.log(res),这个函数Push进promise内部数组去,等异步完成后执行其resolve函数后会顺带把数组里的函数拿出来执行,这样就完成了在异步后调用的效果。
- 注意push的这个函数其实就是利用柯里化的思想,并没有立即执行,等待时机去执行。
- 有人会问为什么要写
if (this.state==='pending')
?这个问题好。我们都知道promise的状态是只要改变后就不会再变回来,也就是只要变成resolve就不会变成reject或者变回pending,我们在内部有个try{}catch(),如果在new一个Promise时中间写function(resolve,reject){resolve();throw new Error()}
那么它岂不是先状态变成成功,然后被trycatch捕获异常又把状态变成reject?,所以这里限定Pending后状态就不会随意改变了。 - 我们再梳理下同步执行顺序,如果是同步操作,excuter立即执行resolve操作,将状态改变,到.then处状态变为resolve或者reject,立即执行.then的操作。这个没什么毛病。
- 有人可能问,为什么要数组存着呢?直接放空函数,等异步调用直接赋成新函数不是也可以吗?这个问题我也思考过,实测这种情况会有852通过20失败,报错为
then may be called multiple times on the same promise.
。也就是说类似下面这种情况就会给一个实例叠加多个function:
let p = new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve(11)
}, 11000);
})
p.then('xx');
p.then('ii')
三、实现链式调用功能
- 很多地方说最难的是链式调用,实际链式调用并不难,最难的在后面,链式调用只要返回一个promise即可链式调用。
function myPromise(executer){
this.state='pending';
this.value=undefined;
this.reason=undefined;
let that = this;
this.resolveArr=[];
this.rejectArr =[];
function resolve(value){
if(that.state==='pending'){
that.state='resolved'
that.value=value;
that.resolveArr.forEach(fn=>fn())
}
};
function reject(reason){
if(that.state==='pending'){
that.state='rejected'
that.reason = reason;
that.rejectArr.forEach(fn=>fn())
}
}
try{
executer(resolve,reject)
}catch(e){
reject(e)
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
let that = this;
let promise2 =new myPromise(function(resolve,reject){
if(that.state==='pending'){
that.resolveArr.push(()=>{
try{
let x = onFullfilled(that.value)
resolve(x);
}catch(e){
reject(e)
}
})
that.rejectArr.push(()=>{
try{
let x = onRejected(that.reason)
resolve(x)
}catch(e){
reject(e)
}
})
}
if(that.state==='resolved'){
try{
let x = onFullfilled(that.value)
resolve(x);
}catch(e){
reject(e);
}
}
if(that.state==='rejected'){
try{
let x = onRejected(that.reason)
resolve(x);
}catch(e){
reject(e);
}
}
})
return promise2
}
let f= new myPromise(function(resolve,reject){
setTimeout(() => {
resolve('value')
}, 1000);
}).then(res=>console.log(res)).then(res=>console.log(res))
let p=new myPromise(function(resolve,reject){
resolve('xxx')
}).then(res=>console.log(res)).then(res=>console.log(res));
代码解读
- 链式调用就是.then后面还能接.then,jq也是能链式的,它的实现就是return一个this,但是为什么Promise不能return一个this呢?因为promise状态不可变,再返回this实际就是这个Promise状态已经变更了,所以需要new一个新的promise再return出来给下一个.then。
- 所以我们在.then里面new 一个新Promise,叫Promise2,并且要绑定下this,因为在Promise2里面的this就会变为global,另外,我们把.then的resolve(res)和reject(res)抽离出来,变成x,也便于理解,因为x的返回值就是.then中的返回值。执行x就是执行then后面的函数。
- 那么,为什么要在onRejected里执行resolve呢?因为就算这个.then的Promise走了reject,那么这个reject没出错,下一个.then就是resolve,这也是Promise的性质。
- 为什么要加trycatch?其实我们在实现同步功能的最后一小点已经做出了解释,这个其实也相当于第一个Promise中的try/catch/excuter(resolve,reject)。
- 有人可能会问,如果每个条件下面写return new Promise 行不行?就相当于下面的代码:
if(that.status==='pending'){
return new myPromise(function(resolve,reject){
try{
that.resolveArr.push(()=>resolve(onFullfilled(that.value)))
}
catch(e){
that.rejectArr.push(()=>reject(onRejected(e)))
}
}
)}
- 这种写法其实也是可以的,同样也是返回了一个新promise,后面也可以跟.then。
四、实现返回值类型判断
- 这是promise当中最难的地方,也是当初我各种找资料困扰了很久的地方。这里我尽量写详细点。
- 到现在为止,我们写的promise有几个缺陷,其中最大的缺陷就是.then的返回值也就是x,它也有可能是个promise,或者是甚至是promise里包个promise,正统的promise会把then里返回值的Promise全部拆解开来调用下一个.then,而我们写的promise完全不会拆开,直接返回个promise{ < pending> }过来。
- 测试例子:可以拿去试验下:
let f= new myPromise(function(resolve,reject){
setTimeout(() => {
resolve('value')
}, 1000);
}).then(res=>new myPromise((resolve,reject)=>{setTimeout(() => {
resolve(res)
}, 1000);})).then(res=>console.log(res))
myPromise {
state: ‘pending’,
value: undefined,
reason: undefined,
resolveArr: [],
rejectArr: [] }
- 原版对照:
let f= new Promise(function(resolve,reject){
setTimeout(() => {
resolve('value')
}, 1000);
}).then(res=>new Promise((resolve,reject)=>{setTimeout(() => {
resolve(res)
}, 1000);})).then(res=>console.log(res))
let p= new Promise(function(resolve,reject){
setTimeout(() => {
resolve('1111')
}, 1000);
}).then(res=>new Promise((resolve,reject)=>{setTimeout(() => {
resolve(new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(res)
}, 1000);
}))
}, 1000);})).then(res=>console.log(res))
value
1111
- 另外还有些细节方面,比如实例不能是自身:
let p= new myPromise(function(resolve,reject){
resolve('xx')
})
let p2=p.then(res=>p2)
p2.then(res=>console.log(res),res=>console.log(res))
- 原版对照:
let p= new Promise(function(resolve,reject){
resolve('xx')
})
let p2=p.then(res=>p2)
p2.then(res=>console.log(res),res=>console.log(res))
TypeError: Chaining cycle detected for promise #<Promise>
- 目前的promise也是无法通过promiseA+的。
- 下面开始一步步解释,我个人觉得后面的思路正常人都想不到,不知道当初创建Promise的人咋想出来的。
- 首先因为x有可能是个promise返回值,所以我们新建个函数resolvePromise替换原来的resolve(x)。
- resolvePromise为什么有promise2,x,resolve,reject4个参数?有promise2是为了后面判断我们这个x它不是promise2,否则像原版就会报
TypeError: Chaining cycle detected for promise
。x就是为了类型判断是不是Promise或者Promise里再包着Promise。resolve和reject是为了处理满足什么条件跳resolve,什么条件跳reject。 - 我们先将then函数改写成这样:
myPromise.prototype.then=function(onFullfilled,onRejected){
let that = this;
let promise2 =new myPromise(function(resolve,reject){
if(that.state==='pending'){
that.resolveArr.push(()=>{
try{
let x = onFullfilled(that.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
that.rejectArr.push(()=>{
try{
let x = onRejected(that.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
}
if(that.state==='resolved'){
try{
let x = onFullfilled(that.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e);
}
}
if(that.state==='rejected'){
try{
let x = onRejected(that.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e);
}
}
})
return promise2
}
function resolvePromise(promise2,x,resolve,reject){
resolve(x)
}
- 有人会问,为什么catch里的reject不用resolvePromise处理?这个原版本来就是这样,失败了再去判断就没什么意义了。 所以在resolvePromise函数里,有些特殊情况,也就直接reject处理。
- 有人会问,你在resolvePromise中传入promise2,这个promise2是最后返回出来的,还没返回就能取到?这个问题问的好。我一开始看见这个操作感觉真尼玛牛b,还没返回的东西还能取到值?这个是什么骚操作?这个东西就涉及js的event loop概念,简单说就是js中有些函数是异步操作的,js会给这些异步操作搞一个队列,当然不同环境event loop略微有点不一样。先看一个例子:
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
- nodejs中运行结果:
1
4
7
5
2
6
3
- 浏览器中运行结果:
1
4
7
5
2
3
6
- 解释下原因:event loop中有3个东西,一个叫全局脚本,一个叫宏队列,一个叫微队列,宏队列和微队列都是异步队列,浏览器中有一个宏队列和一个微队列,Nodejs中有4个宏队列和2个微队列。一般来说,会先执行全局脚本,中间碰上异步的该插入宏队列插入宏队列,插入微队列插入微队列,全局执行完,先开始执行微队列,再开始执行宏队列。
- 在nodejs中与浏览器中,我们发现最后的36和63顺序不一样。settimeout无论在哪个环境中都属于宏队列,而微队列是率先执行的,个人猜想,真正的Promise在nodejs和浏览器中的.then处实现略有区别,nodejs中先出6再出3,个人觉得很可能是.then相当于又增加一个宏队列插入到下方那个settimeout宏队列后面,导致这个结果。而浏览器中的.then则是加入的是微队列,比下方settimeout宏队列率先执行。但这个浏览器目前插入微队列是只有
Object.observe
MutationObserver
这2方法,实在想不出如何做到的,Nodejs倒是可以用proccess.nexttick()来实现插入微队列,但nodejs上的.then明显是宏队列,用settimeout即可。 - 有人可能要问,如果微队列又生成个微队列岂不是宏队列永远无法执行了?这个问题别人早考虑了,所以这个微队列递归调用还有个次数上限。
- 于是我们继续修改代码:
myPromise.prototype.then=function(onFullfilled,onRejected){
let that = this;
let promise2 =new myPromise(function(resolve,reject){
if(that.state==='pending'){
that.resolveArr.push(()=>{
setTimeout(() => {
try{
let x = onFullfilled(that.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
});
})
that.rejectArr.push(()=>{
setTimeout(() => {
try{
let x = onRejected(that.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
});
})
}
if(that.state==='resolved'){
setTimeout(() => {
try{
let x = onFullfilled(that.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e);
}
});
}
if(that.state==='rejected'){
setTimeout(() => {
try{
let x = onRejected(that.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e);
}
});
}
})
return promise2
}
function resolvePromise(promise2,x,resolve,reject){
console.log(promise2,x,resolve,reject)
resolve(x)
}
- 将所有处理上全加上settimeout来达到效果,resolvepromise时可以看一下是否4个值已全部取到。
- 还记得前面那个x等于promise2原版产生的报错吗?那么就在resolvePromise中比较一下,如果相等就抛出异常。
- 做完这个,该判断x到底是不是promise了,但promise情况比较复杂,我们先判断是常量还是一个obj或者function。这里有人会问为啥判断是不是function?所有promise new出来不是obj吗?其实这是作者为了套接某些函数具有相同性质的then方法所弄的,感觉做这种给别人使用的东西就必须很要有这种思想啊,一般人直接判断是不是Obj,不是Obj就直接给你返回就完事了。
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'))
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => resolve(y), r => reject(r))
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
} else {
resolve(x)
}
}
- 这个null去看类型也是obj,所以要把Null给排除掉,有Null也直接抛出即可。
- 有人问为什么这里要加trycatch?因为我们取then的时候有可能报错,比如传进来的东西它原本代码是这么写的:
let obj={};
Object.defineProperty(obj,'then',{
get(){
throw new Error('xxx')
}
})
- 那么有人问为什么写
then.call(x,y=>resolve(y),r=>reject(r))
?这种跟直接x.then(y=>resolve(y),(r)=>reject(r))
有区别吗?答案是当然有区别,因为这样不会重复取两次,还记得前面有篇文章说的调用指定次数后执行的函数吗?结合上面的例子,调用2次后throw个error不就又挂了。 - 这些类型检测一般正常人想不到,个人觉得promise作者是想去兼容别人写的then所留的缺口,否则直接进行判断一棍子打死不就完了?
- 下面还有个难点,如果promise里面还包个promise那么如何处理?这里就需要递归了:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'))
}
let called;
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if(called)return;
called=true;
resolvePromise(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)
}
}
- 这个地方特别烧脑,先说一下测试例子,then()里面是new 一个promise,然后promise里面resolve一个new promise。就是前面讲的嵌套promise那个例子。这么看来,y就是那个嵌套的第二个new promise,目标就是把y进行层层解析,只要是Promise就递归下去,直到返回出常量或者函数。其实Promise主要看.then。x是promise进来,运行y就是x的promise.then,那么y进入递归进入下一层,也就跟x一样,始终能拆成resolve一个常量。
- 另外为什么要加上called?因为这个promise可能是别人写的,这就导致一开始在第一节解释pending说的一个问题,有可能走完resolve又回来走reject的情况,如果使用called的话,就保证走完resolve不会跳回来走reject了,而如果是常量,那么就不存在成功或者失败的问题。
- 这个时候可以拿这个例子测试下,应该能正常打印出来了:
let p= new myPromise(function(resolve,reject){
setTimeout(() => {
resolve('1111')
}, 1000);
}).then(res=>new myPromise((resolve,reject)=>{setTimeout(() => {
resolve(new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve(res)
}, 1000);
}))
}, 1000);})).then((res)=>console.log(res))
- 你以为结束了吗?还没完,目前的promise拿去测试是831通过41失败。
- 下面还需要解决then的透传问题,看测试例子:
let p= new myPromise(function(resolve,reject){
setTimeout(() => {
resolve('1111')
}, 1000);
}).then().then((res)=>console.log(res))
- 什么叫透传?就是第一个then没传东西,下一个then继续处理。我们的promise运行这个测试例子啥都不会打印,而正版的就能打印出1111来。
- 其实原理很简单,就是如果没传值的话,就return参数即可把函数传给下一个.then。
- 这里有个点需要注意下:走reject话不能return错误,而是应该throw错误,让后一个.then也走reject。
- 我们就在前面加上2句:
myPromise.prototype.then = function (onFullfilled, onRejected) {
let that = this;
onFullfilled=typeof onFullfilled ==='function'?onFullfilled:val=>val;
onRejected=typeof onRejected ==='function'?onRejected:err=>{throw err};
let promise2 = new myPromise(function (resolve, reject) {
if (that.state === 'pending') {
that.resolveArr.push(() => {
setTimeout(() => {
try {
let x = onFullfilled(that.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
});
})
that.rejectArr.push(() => {
setTimeout(() => {
try {
let x = onRejected(that.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
});
})
}
if (that.state === 'resolved') {
setTimeout(() => {
try {
let x = onFullfilled(that.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
});
}
if (that.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(that.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
});
}
})
return promise2
}
- 到此处再去执行promiseA+测试就能872全通过了!
- 但是目前这个Promise还有Bug!
- 我们前面用递归解决了.then中返回的promise里resolve再嵌套Promise的的情况,但是没有解决第一个Promise里resolve嵌套Promise的情况。看下面这个测试例子:
let p= new myPromise(function(resolve,reject){
setTimeout(() => {
resolve(new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve('11111')
}, 1000);
}))
}, 1000);
}).then((res)=>console.log(res))
myPromise {
state: ‘pending’,
value: undefined,
reason: undefined,
resolveArr: [],
rejectArr: [] }
- 可以发现我们的Promise打印出来就是个对象,而正版的就会把1111给全输出。
- 这里的思路跟resolvePromise的思路一样,也是使用递归。
function myPromise(executer) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
let that = this;
this.resolveArr = [];
this.rejectArr = [];
function resolve(value) {
if(value instanceof myPromise){
return value.then(resolve,reject)
}
if (that.state === 'pending') {
that.state = 'resolved'
that.value = value;
that.resolveArr.forEach(fn => fn())
}
};
function reject(reason) {
if (that.state === 'pending') {
that.state = 'rejected'
that.reason = reason;
that.rejectArr.forEach(fn => fn())
}
}
try {
executer(resolve, reject)
} catch (e) {
reject(e)
}
}
- 主要就是看resolve(value)中的value是不是promise,如果是promise就解析then,当然它这个还有个bug,如果这个value是别人的promise实例照样打印不出来,不过这个如果要进行判断就比较麻烦了。
五、实现catch方法
- 这个catch比较简单,其实是then(null,callback)的语法糖。可以照着then一样写个函数:
myPromise.prototype.catch=function(errcallback){
return this.then(null,errcallback);
}
六、实现promise.resolve/reject方法
- 这个是直接调用类名而不是实例对象的操作,所以是静态方法:
myPromise.resolve=function(value){
return new myPromise((resolve,reject)=>{
resolve(value)
})
}
myPromise.reject=function(reason){
return new myPromise((resolve,reject)=>{
reject(reason)
})
}
七、实现promise.all方法
- 这个就是传一个数组,所有都执行完就返回数组。
- 这个也要进行类型判断,不过经过前面resolvePromise函数的编写,这个地方怎么判断感觉都会有bug。于是就随意点,自由发挥,当然也可以写是promise实例什么的。
myPromise.all= function (promises) {
function isPromise(x){
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try{
let s= typeof x.then ==='function';
return s;
}catch(e){
return false
}
}else{
return false
}
}
return new myPromise((resolve,reject)=>{
let arr = [];
let p=0;
let proccessData=(index,data)=>{
arr[index]=data;
p++;
if(p===promises.length){
resolve(arr)
}
}
for(let i=0;i<promises.length;i++){
let current= promises[i];
if(isPromise(current)){
current.then(data=>{
proccessData(i,data)
},reject)
}else{
proccessData(i,current)
}
}
})
}
- 这里面一个坑就是最终这个promise的resolve条件上,由于异步完成才会调用proccessdata函数,所以我们不能写index===promises.length,而应该重新声明个变量,每有调用加一即可。
- 测试例子:
let p =function () {
return new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve(11)
}, 11000);
})
}
let f =function () {
return new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve(22)
}, 12000);
})
}
myPromise.all([1,p(),2,f(),3]).then(res=>console.log(res))
[ 1, 11, 2, 22, 3 ]
八、实现ES6写法
- ES6写法就稍微改变下,剩下的就是复制粘贴。
- 其中then方法由于在class类里其实是可以不需要绑定this。
- 另外就是分清楚静态方法原型方法复制粘贴的位置即可。
- resolvePromise函数跟上面一模一样,都不用改。
- 已测试可以通过A+。
class myPromise{
constructor(executer){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
let that = this;
this.resolveArr = [];
this.rejectArr = [];
function resolve(value) {
if(value instanceof myPromise){
return value.then(resolve,reject)
}
if (that.state === 'pending') {
that.state = 'resolved'
that.value = value;
that.resolveArr.forEach(fn => fn())
}
};
function reject(reason) {
if (that.state === 'pending') {
that.state = 'rejected'
that.reason = reason;
that.rejectArr.forEach(fn => fn())
}
}
try {
executer(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFullfilled,onRejected) {
let that = this;
onFullfilled=typeof onFullfilled ==='function'?onFullfilled:val=>val;
onRejected=typeof onRejected ==='function'?onRejected:err=>{throw err};
let promise2 = new myPromise(function (resolve, reject) {
if (that.state === 'pending') {
that.resolveArr.push(() => {
setTimeout(() => {
try {
let x = onFullfilled(that.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
});
})
that.rejectArr.push(() => {
setTimeout(() => {
try {
let x = onRejected(that.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
});
})
}
if (that.state === 'resolved') {
setTimeout(() => {
try {
let x = onFullfilled(that.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
});
}
if (that.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(that.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
});
}
})
return promise2
}
catch(errcallback){
return this.then(null,errcallback);
}
static resolve(value){
return new myPromise((resolve,reject)=>{
resolve(value)
})
}
static reject(reason){
return new myPromise((resolve,reject)=>{
reject(reason)
})
}
}
let p = new myPromise((resolve,reject)=>{
setTimeout(() => {
reject(11)
}, 11000);
})
p.then('xx').catch((e)=>console.log(e));
myPromise.resolve('111').then((res)=>console.log(res));
九、测试promiseA+方法
- 这个是为防止有人不知道额外加的。
- 首先npm安装
promises-aplus-tests -g
- 使用
promises-aplus-tests 你的文件名.js
- 文件里需要这么导出:
myPromise.deferred = function () {
let dfd={}
dfd.promise = new myPromise((resolve,reject)=>{
dfd.resolve=resolve;
dfd.reject=reject;
})
return dfd
}
module.exports = myPromise;