由浅入深,掌握Promise

为叙述方便,以下代码全部将fulfilled状态改为resolved状态。

1 简易版

js 复制代码
 function Promise(executor) {
   let self = this;
   self.status = "pending"; // 定义状态改变前的初始状态
   self.value = undefined; // 定义状态为resolved的时候的状态
   self.reason = undefined; //定义状态为rejected的时候的状态
 ​
   function resolve(value) {
     // 将状态由 pending --> resolved
     if (self.status === "pending") {
       self.value = value;
       self.status = "resolved";
     }
   }
 ​
   function reject(reason) {
     // 将状态由 pending --> rejected
     if (self.status === "pending") {
       self.reason = reason;
       self.status = "rejected";
     }
   }
 ​
   //捕获构造异常
   try {
     executor(resolve, reject);
   } catch (e) {
     reject(e);
   }
 }
 ​
 // 定义链式调用的then方法
 Promise.prototype.then = function (onResolved, onRejected) {
   let self = this;
   // 校验 onResolved 和 onRejected
   onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
   // prettier-ignore
   onRejected = typeof onRejected === "function" ? onRejected : (err) => { throw err; };
   switch (self.status) {
     case "resolved":
       onResolved(self.value);
       break;
     case "rejected":
       onRejected(self.reason);
       break;
     default:
       break;
   }
 };

缺陷

  • 简易版只能处理同步的情况,在new Promise()构造函数里,如果有异步调用,则无法处理。

    js 复制代码
     var p = new Promise((resolve, reject) => {
       console.log(1);
       setTimeout(() => {
         resolve("异步");
       });
     });
     ​
     p.then((x) => {
       console.log(x);
     });
     // 最终只打印1,没有打印异步

2 修复构造函数中异步处理

问题原因

由于构造函数里的resolve是异步,因此p.then的回调要更先执行,而此时的Promise的状态还是pending(resolve是异步的,还没执行),因此then方法里的case匹配失败。

解决方案

  1. 修改then方法,pending状态时收集回调函数。
  2. resolve或者reject时,遍历执行pending状态时收集的回调函数。
js 复制代码
 function Promise(executor) {
   let self = this;
   self.status = "pending"; // 定义状态改变前的初始状态
   self.value = undefined; // 定义状态为resolved的时候的状态
   self.reason = undefined; //定义状态为rejected的时候的状态
 ​
   self.onResolvedCallbacks = []; // 1.1:定义pending状态下需存放的resolved回调函数
   self.onRejectedCallbacks = []; // 1.2:定义pending状态下需存放的rejected回调函数
 ​
   function resolve(value) {
     // 将状态由 pending --> resolved
     if (self.status === "pending") {
       self.value = value;
       self.status = "resolved";
 ​
       // 2.1: 一旦 resolve 执行,遍历执行已收集到的resolved回调函数
       self.onResolvedCallbacks.forEach((fn) => fn());
     }
   }
 ​
   function reject(reason) {
     // 将状态由 pending --> rejected
     if (self.status === "pending") {
       self.reason = reason;
       self.status = "rejected";
 ​
       // 2.2: 同样,一旦 reject 执行,遍历执行已收集到的rejected回调函数
       self.onRejectedCallbacks.forEach((fn) => fn());
     }
   }
 ​
   //捕获构造异常
   try {
     executor(resolve, reject);
   } catch (e) {
     reject(e);
   }
 }
 ​
 // 定义链式调用的then方法
 Promise.prototype.then = function (onResolved, onRejected) {
   let self = this;
   onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
   // prettier-ignore
   onRejected = typeof onRejected === "function" ? onRejected : (err) => { throw err; };
   switch (self.status) {
     case "resolved":
       onResolved(self.value);
       break;
     case "rejected":
       onRejected(self.reason);
       break;
     case "pending":
       // 1.3: pending状态时收集当前onResolved方法
       self.onResolvedCallbacks.push(() => {
         onResolved(self.value);
       });
       // 1.4: pending状态时同样需要收集当前onRejected方法
       self.onRejectedCallbacks.push(() => {
         onRejected(self.reason);
       });
     default:
       break;
   }
 };

执行以下例子:

js 复制代码
 const p = new Promise((resolve, reject) => {
   console.log(1);
   setTimeout(() => {
     resolve("异步");
   });
 });
 ​
 p.then((x) => {
   console.log(x);
 });
 // 1
 // 异步

再次执行上述例子,会发现已经能打印1异步

缺陷

  • 无法链式调用

    js 复制代码
     const p = new Promise((resolve, reject) => {
       resolve(1);
     });
     ​
     p.then((x) =>{
       console.log(x);
     }).then(() => {
       console.log(2)
     });
     // 报错

3 修复链式调用

问题原因

若要链式调用then方法的话,需要then返回一个Promise对象,而我们前面这版并没有做到这点,故无法链式调用then方法。

解决方案

  1. then方法中返回一个新的Promise(称为P1), P1的状态由回调函数决定。

    • 如果回调函数返回的结果是一个 Promise 对象(称为P2),那么P1 的状态将与P2 对象的状态相同。
    • 如果回调函数返回的结果不是一个Promise对象,则用返回的结果resolve掉P1。
    • 如果回调函数抛出错误,那么P1的状态将为 rejected,用try catch捕获错误达到将P1状态改为rejected
js 复制代码
 function Promise(executor) {
   // ...上一版的Promise构造函数代码
 }
 Promise.prototype.then = function (onResolved, onRejected) {
   let self = this;
   onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
   onRejected = typeof onRejected === "function" ? onRejected : (err) => { throw err; };
 ​
   // 1.1 返回新的Promise对象(称为P1)
   return new Promise((resolve, reject) => {
     const callback = (fn) => {
       try {
         // 执行then方法传入的onResolved或者onRejected回调,得到结果result
         const result = fn(self.value || self.reason);
         if (result instanceof Promise) {
           // 1.2 返回的结果是一个 Promise 对象(称为P2),那么P1 的状态将与P2 对象的状态相同。
           // prettier-ignore
           result.then((res) => resolve(res), (err)=> reject(err))
         }
         // 1.3 返回的结果不是一个Promise对象,则用返回的结果resolve掉P1。
         resolve(result);
       } catch (err) {
         // 1.4 报错则用报错信息reject掉P1
         reject(err);
       }
     };
 ​
     switch (self.status) {
       case "resolved":
         callback(onResolved);
         break;
       case "rejected":
         callback(onRejected);
         break;
       case "pending":
         self.onResolvedCallbacks.push(() => { callback(onResolved); });
         self.onRejectedCallbacks.push(() => { callback(onRejected); });
       default:
         break;
     }
   });
 };

再次执行以下例子:

js 复制代码
 const p = new Promise((resolve, reject) => {
   resolve(1);
 });
 ​
 p.then((x) =>{
   console.log(x);
 }).then(() => {
   console.log(2)
 });
 // 1
 // 2

已成功打印出1和2,说明我们的then链式调用改造成功。

至此,一个简易版的Promise已经实现,当然还有很多边界情况我们没有考虑:如

  • then方法里返回的如果是具有then方法的对象或者函数、那么也是可以当做一个Promise来对待的。
  • then方法是异步的。这点需要通过setTimeout来优化我们的then方法。
  • 还有其他情况等等。

4 catch race all方法

实现了Promisethen方法,那么其他方法就比较简单了,如catch方法其实是一个没有第一个入参的then方法。

catchPromise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数

js 复制代码
 Promise.prototype.catch = function (onRejected) {
   return this.then(null, onRejected);
 };

测试:

js 复制代码
 const p = new Promise((resolve, reject) => {
   reject("err");
 });
 ​
 p.catch((res) => {
   console.log(res);
 });
 // 输出:err

racerace方法将多个 Promise 实例,包装成一个新的 Promise 实例。率先改变状态的Promise实例,其返回值将传回给回调函数。

js 复制代码
 Promise.prototype.race = function (promises) {
   return new Promise((resolve, reject) => {
     promises.forEach((promise) => {
       promise.then(resolve, reject);
     });
   });
 };

测试:

js 复制代码
 const p = new Promise((resolve, reject) => {
   resolve();
 });
 ​
 const p1 = new Promise((resolve, reject) => {
   setTimeout(() => {
     resolve("p1");
   }, 300);
 });
 ​
 const p2 = new Promise((resolve, reject) => {
   setTimeout(() => {
     resolve("p2");
   }, 200);
 });
 ​
 const p3 = new Promise((resolve, reject) => {
   setTimeout(() => {
     resolve("p3");
   }, 100);
 });
 ​
 p.race([p1, p2, p3]).then((res) => {
   console.log(res);
 });
 // 输出:p3

allall方法同样将多个 Promise 实例,包装成一个新的 Promise 实例。 只不过是需要所有的Promise实例的状态都resolved后,其返回值组成的数组才传回给回调函数;或最先rejectedPromise实例,其返回值传给回调函数。

js 复制代码
 Promise.prototype.all = function (promises) {
   const result = [];
   let resolvedCount = 0;
   return new Promise((resolve, reject) => {
     for (let i = 0; i < promises.length; i++) {
       promises[i].then(
         (res) => {
           // 这里不能用push,因为要保证顺序
           result[i] = res;
           resolvedCount += 1;
           // 这里不能用result.length去判等,因为result不是用push保存的
           if (resolvedCount === promises.length) {
             resolve(result);
           }
         },
         (err) => reject(err)
       );
     }
   });
 };

测试:

js 复制代码
 const p = new Promise((resolve, reject) => {
   resolve();
 });
 ​
 const p1 = new Promise((resolve, reject) => {
   setTimeout(() => {
     resolve("p1");
   }, 300);
 });
 ​
 const p2 = new Promise((resolve, reject) => {
   setTimeout(() => {
     resolve("p2");
   }, 200);
 });
 ​
 const p3 = new Promise((resolve, reject) => {
   setTimeout(() => {
     resolve("p3");
   }, 100);
 });
 ​
 p.all([p1, p2, p3]).then((res) => {
   console.log(res);
 });
 // 输出:[ 'p1', 'p2', 'p3' ]

5 完整代码

js 复制代码
 function Promise(executor) {
   let self = this;
   self.status = "pending"; // 定义状态改变前的初始状态
   self.value = undefined; // 定义状态为resolved的时候的状态
   self.reason = undefined; //定义状态为rejected的时候的状态
 ​
   self.onResolvedCallbacks = []; // 1.1:定义pending状态下需存放的resolved回调函数
   self.onRejectedCallbacks = []; // 1.2:定义pending状态下需存放的rejected回调函数
 ​
   function resolve(value) {
     // 将状态由 pending --> resolved
     if (self.status === "pending") {
       self.value = value;
       self.status = "resolved";
 ​
       // 2.1: 一旦 resolve 执行,遍历执行已收集到的resolved回调函数
       self.onResolvedCallbacks.forEach((fn) => fn());
     }
   }
 ​
   function reject(reason) {
     // 将状态由 pending --> rejected
     if (self.status === "pending") {
       self.reason = reason;
       self.status = "rejected";
 ​
       // 2.2: 同样,一旦 reject 执行,遍历执行已收集到的rejected回调函数
       self.onRejectedCallbacks.forEach((fn) => fn());
     }
   }
 ​
   //捕获构造异常
   try {
     executor(resolve, reject);
   } catch (e) {
     reject(e);
   }
 }
 ​
 Promise.prototype.then = function (onResolved, onRejected) {
   let self = this;
   onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
   // prettier-ignore
   onRejected = typeof onRejected === "function" ? onRejected : (err) => { throw err; };
 ​
   // 1.1 返回新的Promise对象
   return new Promise((resolve, reject) => {
     const callback = (fn) => {
       try {
         // 执行then方法传入的onResolved或者onRejected回调,得到结果result
         const result = fn(self.value || self.reason);
         if (result instanceof Promise) {
           // 1.2 返回的结果是一个 Promise 对象(称为P2),那么P1 的状态将与P2 对象的状态相同。
           // prettier-ignore
           result.then((res) => resolve(res), (err)=> reject(err))
         }
         // 1.3 返回的结果不是一个Promise对象,则用返回的结果resolve掉P1。
         resolve(result);
       } catch (err) {
         // 1.4 报错则用报错信息reject掉P1
         reject(err);
       }
     };
 ​
     switch (self.status) {
       case "resolved":
         callback(onResolved);
         break;
       case "rejected":
         callback(onRejected);
         break;
       case "pending":
         // prettier-ignore
         self.onResolvedCallbacks.push(() => { callback(onResolved); });
         // prettier-ignore
         self.onRejectedCallbacks.push(() => { callback(onRejected); });
       default:
         break;
     }
   });
 };
 ​
 Promise.prototype.catch = function (onRejected) {
   return this.then(null, onRejected);
 };
 ​
 Promise.prototype.race = function (promises) {
   return new Promise((resolve, reject) => {
     promises.forEach((promise) => {
       promise.then(resolve, reject);
     });
   });
 };
 ​
 Promise.prototype.all = function (promises) {
   const result = [];
   let resolvedCount = 0;
   return new Promise((resolve, reject) => {
     for (let i = 0; i < promises.length; i++) {
       promises[i].then(
         (res) => {
           // 这里不能用push,因为要保证顺序
           result[i] = res;
           resolvedCount += 1;
           // 这里不能用result.length去判等,因为result不是用push保存的
           if (resolvedCount === promises.length) {
             resolve(result);
           }
         },
         (err) => reject(err)
       );
     }
   });
 };

如有错误,欢迎大家指出~(抱拳啦!)

相关推荐
噢,我明白了3 小时前
同源策略:为什么XMLHttpRequest不能跨域请求资源?
javascript·跨域
sanguine__3 小时前
APIs-day2
javascript·css·css3
关你西红柿子3 小时前
小程序app封装公用顶部筛选区uv-drop-down
前端·javascript·vue.js·小程序·uv
济南小草根4 小时前
把一个Vue项目的页面打包后再另一个项目中使用
前端·javascript·vue.js
小木_.4 小时前
【python 逆向分析某有道翻译】分析有道翻译公开的密文内容,webpack类型,全程扣代码,最后实现接口调用翻译,仅供学习参考
javascript·python·学习·webpack·分享·逆向分析
Aphasia3114 小时前
一次搞懂 JS 对象转换,从此告别类型错误!
javascript·面试
m0_748256565 小时前
Vue - axios的使用
前端·javascript·vue.js
m0_748256345 小时前
QWebChannel实现与JS的交互
java·javascript·交互
胡西风_foxww5 小时前
【es6复习笔记】函数参数的默认值(6)
javascript·笔记·es6·参数·函数·默认值
胡西风_foxww5 小时前
【es6复习笔记】生成器(11)
javascript·笔记·es6·实例·生成器·函数·gen