手写JS实现Promise

互联网 20-5-7
Promise概览

Promise是一个管理异步编程的方案,它是一个构造函数,每次使用可用new创建实例;它有三种状态:pending、fulfilled和rejected,这三种状态不会受外界影响,状态只能由pending变为fullfilled(成功),pending变为rejected(失败),且一旦改变就不会再改变,在状态改变后,它会返回成功的结果或者失败的原因,它对外抛出了resolve、reject、catch、finally、then、all、race、done,在最新的提案中,添加了allSettled方法,它不管成功、失败都会返回,接下来,我们自己实现整个Promise

executor函数

我们知道,在创建一个Promise实例时,都会立即执行executor函数,executor函数传递两个参数,resolve和reject,如果executor函数执行错误,Promise实例状态会变为rejected

class MyPromise{     constructor(executor) {         this.status = "pending";     // 初始化状态为pending         this.value = undefined;      // 初始化返回的成功的结果或者失败的原因                  // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回         let resolve = result => {             if(this.status !== "pending") return;  // 状态一旦改变,就不会再变             this.status = "resolved";             this.value = result;         }                  // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回         let reject = reason => {             if(this.status !== "pending") return;             this.status = "rejected";             this.value = reason;         }         // try、catch捕获异常,如果错误,执行reject方法         try {             executor(resolve, reject)         } catch(err) {             reject(err)         }     } }

我们来验证一下,现在的Promise是什么样的

let p1 = new MyPromise((resolve, reject) => {     resolve(1); }) let p2 = new MyPromise((resolve, reject) => {     reject(2); }) console.log(p1); console.log(p2);

可以看到,状态已经改变了,里面的值也是成功的结果和失败的原因。then方法有两个参数,第一个参数是成功时执行的,第二个参数为失败后执行的,then的链式调用和数组等是一样的,每次执行后会返回一个Promise实例。如果成功后,第一个then中成功的函数为null,它会继续向下查找,直至不为null的函数执行,上一个then中返回的结果会直接影响下一个then中执行成功或者失败的哪个函数,了解了这些之后,我们尝试实现一下~

then方法

then(resolveFn, rejectFn) {     // 如果传入的两个参数不是函数,则直接执行返回结果     let resolveArr = [];     let rejectArr = [];          if(typeof resolveFn !== "function") {         resolveFn = result => {             return result;         }     }          if(typeof rejectFn !== "function") {         rejectFn = reason => {             return MyPromise.reject(reason);         }     }          return new Mypromise((resolve, reject) => {         resolveArr.push(result => {             try {                 let x = resolveFn(result);                                  if(x instanceof MyPromise) {                     x.then(resolve, reject)                     return;                 }                                  resolve(x);             } catch(err) {                 reject(err)             }         })                  rejectArr.push(reason => {             try {                 let x = rejectFn(reason);                                  if(x instanceof MyPromise) {                     x.then(resolve, reject)                     return;                 }                                  resolve(x);             } catch(err) {                 reject(err)             }         })     }) }

我们来整理一下上面的代码

class MyPromise{     constructor(executor) {         this.status = "pending";     // 初始化状态为pending         this.value = undefined;      // 初始化返回的成功的结果或者失败的原因         this.resolveArr = [];        // 初始化then中成功的方法         this.rejectArr = [];         // 初始化then中失败的方法                           // 定义change方法,因为我们发现好像resolve和reject方法共同的地方还挺多         let change = (status, value) => {             if(this.status !== "pending") return;  // 状态一旦改变,就不会再变             this.status = status;             this.value = value;                          // 根据状态判断要执行成功的方法或失败的方法             let fnArr = status === "resolved" ? this.resolveArr : this.rejectArr;                          // fnArr中的方法依次执行             fnArr.forEach(item => {                 if(typeof item !== "function") return;                 item(this. value);             })         }         // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回         let resolve = result => {             change("resolved", result)         }                  // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回         let reject = reason => {             change("rejected", reason);         }                  // try、catch捕获异常,如果错误,执行reject方法         try {             executor(resolve, reject)         } catch(err) {             reject(err)         }     }          then(resolveFn, rejectFn) {     // 如果传入的两个参数不是函数,则直接执行返回结果              if(typeof resolveFn !== "function") {             resolveFn = result => {                 return result;             }         }                  if(typeof rejectFn !== "function") {             rejectFn = reason => {                 return MyPromise.reject(reason);             }         }                  return new MyPromise((resolve, reject) => {             this.resolveArr.push(result => {                 try {                     let x = resolveFn(result);  // 获取执行成功方法返回的结果                                          // 如果x是一个promise实例,则继续调用then方法 ==> then链的实现                     if(x instanceof MyPromise) {                         x.then(resolve, reject)                         return;                     }                                          // 不是promise实例,直接执行成功的方法                     resolve(x);                 } catch(err) {                     reject(err)                 }             })                          this.rejectArr.push(reason => {                 try {                     let x = rejectFn(reason);                                          if(x instanceof MyPromise) {                         x.then(resolve, reject)                         return;                     }                                          resolve(x);                 } catch(err) {                     reject(err)                 }             })         })     } }

我们来看一下效果

new MyPromise((resolve, reject) => {     resolve(1); }).then(res => {     console.log(res, 'success'); }, err => {     console.log(err, 'error'); })

这时候,问题出现了,我们发现好像什么也没有输出,如果我们对上面的测试例子做一下小小的改动呢?

new MyPromise((resolve, reject) => {     setTimeout(_ => {         resolve(1);     }, 0) }).then(res => {     console.log(res, 'success');    // 1 "success" }, err => {     console.log(err, 'error'); })

这是因为创建了Promise实例就立即执行了executor函数,还没有执行then方法,那么不管成功还是失败的数组中,都是空的。那可能小伙伴们又有疑问了,为什么加了setTimeout就好使了呢?这是因为在事件队列机制中,setTimeout会放入事件队列中,等主线程执行完成后再执行,此时then方法会存储成功或者失败的函数,所以不管是成功的数组还是失败的数组中都已经有值了,这个时候再去执行就完全了~

但是我们不能在使用的时候写setTimeout当做解决方案呀,既然我们在封装,就要在封装的函数内解决问题,按照这样的思路,我们也同样可以在resolve和reject方法执行的时候,判断数组中是否有值,如果没有,我们可以利用setTimeout让它延后执行,代码如下~

// 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回 let resolve = result => {        // 如果数组中有值,则立即改变状态     if(this.resolveArr.length > 0) {         change("resolved", result)     }     // 如果没值,则延后改变状态     let timer = setTimeout(_ => {         change("resolved", result)         clearTimeout(timer);     }, 0) } // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回 let reject = reason => { // 如果数组中有值,则立即改变状态     if(this.rejectArr.length > 0) {         change("rejected", reason);     }     // 如果没值,则延后改变状态     let timer = setTimeout(_ => {         change("rejected", reason);         clearTimeout(timer);     }, 0) }

现在我们再试一下

// 1、已经成功了 new MyPromise((resolve, reject) => {     resolve('我成功啦,吼吼吼~~~~');                 reject('我都已经成功了,你别想让我失败,哼~~'); }).then(res => {     console.log(res, 'success');         // 我成功啦,吼吼吼~~~~ success }, err => {     console.log(err, 'error'); }) // 2、先失败了 new MyPromise((resolve, reject) => {     reject('失败了,我好委屈,呜呜呜~~');     resolve('已经失败了~~~');             }).then(res => {     console.log(res, 'success');          }, err => {     console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error }) // 3、链式调用 new MyPromise((resolve, reject) => {     reject('失败了,我好委屈,呜呜呜~~');     resolve('已经失败了~~~');             }).then(res => {     console.log(res); }, err => {     console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error     return '我要发奋图强,不会被困难所击倒,我要成功!!!' }).then(res1 => {     console.log(res1, '经过不懈努力,我终于在第二次成功了~');  // 我要发奋图强,不会被困难所击倒,我要成功!!!  经过不懈努力,我终于在第二次成功了~ }, err1 => {     console.log(err1, '第二次失败'); })

这就完美解决了第一次调用,不会执行then方法的问题。同时,实现了链式的调用。对于链式的调用,我多啰嗦两句,其实不管是数组的链式调用,都是因为上一次返回的还是此实例。

catch方法

catch方法是捕获异常,它和then方法的第二个回调函数是一样的

catch(rejectFn) {     return this.then(null, rejectFn) }

resolve方法

我们知道,Promsie也可以这样用

let p1 = MyPromise.resolve(1); console.log(p1);

我们期望有这样一种写法,但是现在肯定会抛出错误:MyPromise.resolve不是一个方法

现在需要我们封装一下resolve方法,我们需要明确的是,resolve之后,Promise是支持再继续链式调用then的,所以,我们需要执行resolve方法,返回一个Promise实例

static resolve(result) {     // 返回新的promise实例,执行promise实例中resolve方法     return new MyPromise(resolve => {         resolve(result)     }) }

reject方法

像resolve方法一样,只不过它接收的是失败的函数

static reject(reason) {     // 返回新的promise实例,执行promise实例中reject方法     return new MyPromise((_, reject) => {         reject(reason);     }) }

done方法

ES6标准入门一书中,对done方法的解释是这样的:无论Promise对象的回调链以then方法还是catch方法结尾,只要最后一个方法抛出错误,都有可能无法捕获到。为此,Promise提供了一个done方法,它总是处于回掉链的尾端,保证抛出任何可能出现的错误。好了,我们知道了这个方法是干啥的,现在就开始写吧~

done(resolveFn, rejectFn) {     this.then(resolveFn, rejectFn)         .catch(reason => {             setTimeout(() => {                 throw reason;             }, 0)         }) }

它可以接收fulfilled、rejected状态的回调函数,也可以不提供任何参数。但是无论怎样,done方法都会捕捉到任何可能出现的错误,并向全局抛出

finally方法

finally方法是无论成功还是失败都会执行的方法,像这样的方法还有小程序中的complete方法等等,我们来尝试实现一下~

finally(finallyFn) {     let P = this.constructor;     return this.then(         value => P.resolve(finallyFn()).then(() => value),         reason => P.reject(finallyFn()).then(() => reason)     ) }

我们来验证一下

new MyPromise((resolve, reject) => {     reject('失败了,我好委屈,呜呜呜~~');     resolve('已经失败了~~~'); }).then(res => {     console.log(res); }, err => {     console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error     return '我要发奋图强,不会被困难所击倒,我要成功!!!' }).finally(() => {     console.log('执行了吗');            // 这里会输出"执行了吗" })

all方法

all方法接收一个数组,当数组中每个实例都成功时才会返回,返回的也是一个数组,每个参数为对应的promise返回的结果,如果有一项失败了,all方法都会返回失败

// 接收数组参数 static all(promiseList) {     // 返回新实例,调用后还可使用then、catch等方法     return new MyPromise((resolve, reject) => {         let index = 0,      // 成功次数计数             results = [];   // 返回的结果                  for(let i = 0; i < promiseList.length; i++) {             let item = promiseList[i];                          // 如果item不是promise实例             if(!(item instanceof MyPromise)) return;                          item.then(result => {                 index++;                 results[i] = result;                 if(index === promiseList.length) {                     resolve(results);                 }             }).catch(reason => {                 reject(reason);             })         }     }) }

来验证一下

// 1.有失败的情况 let p1 = MyPromise.resolve(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.resolve(3); MyPromise.all([p1, p2, p3])     .then(res => {         console.log(res);     }).catch(err => {         console.log(err, 'err');     // 2 "err"     }) // 2.无失败的情况 let p1 = MyPromise.resolve(1); let p2 = MyPromise.resolve(2); let p3 = MyPromise.resolve(3); MyPromise.all([p1, p2, p3])     .then(res => {         console.log(res, 'success');   // [1, 2, 3] "success"     }).catch(err => {         console.log(err, 'err');     })

race方法

race方法同样接收一个数组参数,里面每一项是Promise实例,它返回最快改变状态的Promise实例方法的结果

static race(promiseList) {     return new MyPromise((resolve, reject) => {         promiseList.forEach(item => {             if(!(item instanceof MyPromise)) return;                          item.then(result => {                 resolve(result);             }).catch(err => {                 reject(err)             })         })     }) } 复制代码验证 // 1. let p1 = MyPromise.resolve(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3])     .then(res => {         console.log(res);            // 1 'success'     }).catch(err => {         console.log(err, 'err');         }) // 2. let p1 = MyPromise.reject(1); let p2 = MyPromise.resolve(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3])     .then(res => {         console.log(res, 'success');        }).catch(err => {         console.log(err, 'err');       // 1 'err'     })      // 3. let p1 = MyPromise.reject(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.reject(3); MyPromise.race([p1, p2, p3])     .then(res => {         console.log(res, 'success');        }).catch(err => {         console.log(err, 'err');       // 1 'err'     })

尝试实现allSettled方法

allSettled方法也是接收数组参数,但是它无论成功或者失败,都会返回

static allSettled(promiseList) {     return new MyPromise((resolve, reject) => {         let results = [];                  for(let i = 0; i < promiseList.length; i++) {             let item = promiseList[i];                          if(!(item instanceof MyPromise)) return;                          item.then(result => {                 results[i] = result;             }, reason => {                 results[i] = reason;             })             resolve(results);         }     }) } 复制代码验证 // 1. let p1 = MyPromise.resolve(1); let p2 = MyPromise.resolve(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3])     .then(res => {         console.log(res);            // [1, 2, 3] 'success'     }).catch(err => {         console.log(err, 'err');         }) // 2. let p1 = MyPromise.reject(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.reject(3); MyPromise.race([p1, p2, p3])     .then(res => {         console.log(res, 'success');   // [1, 2, 3] 'success'     }).catch(err => {         console.log(err, 'err');            })      // 3. let p1 = MyPromise.resolve(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3])     .then(res => {         console.log(res, 'success');   // [1, 2, 3] 'success'     }).catch(err => {         console.log(err, 'err');            })

推荐教程:《JS教程》

以上就是手写JS实现Promise的详细内容,更多内容请关注技术你好其它相关文章!

来源链接:
免责声明:
1.资讯内容不构成投资建议,投资者应独立决策并自行承担风险
2.本文版权归属原作所有,仅代表作者本人观点,不代表本站的观点或立场
标签: promise
上一篇:php获取远程图片并下载保存到本地的方法分析 下一篇:js屏蔽pc端访问

相关资讯