由浅入深,掌握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)
       );
     }
   });
 };

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

相关推荐
熊的猫38 分钟前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
别拿曾经看以后~2 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
川石课堂软件测试2 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
JerryXZR3 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
problc3 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
Gavin_9153 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼4 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
待磨的钝刨5 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
前端青山10 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
从兄11 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript