最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 手写Promise记录

    正文概述 掘金(violetrosez)   2021-03-10   518

    前言

    最近复盘面试知识点,问到异步编程的解决方案这一块,发现自己的认识并不清晰,很容易被问倒。本文主要记录了自己实现一个promise的全过程,建议大家配合Promise A+规范一起阅读。

    Promise? what & why

    Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。ES6规范提出Promise对象可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者(语义化更好的aysnc/await只是promise和generator的语法糖。async-await是建立在 promise机制之上的)

    Promise的基本结构

    Promise构造函数接受一个函数作为参数,我们称该函数为excuterexcuter又包含resolve和reject两个参数,它们是两个函数。promise初始化的时候就会执行这个函数,

    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("resolve");
      }, 1000);
    });
    

    Promise基本状态与值

    一个 Promise 必然处于以下几种状态之一:

    1. 待定(pending): 初始状态,在此状态下可以落定 (settled) 为 fulfilled 或 rejected状态。
    2. 已兑现(fulfilled): 意味着操作成功完成,返回一个私有的值value
    3. 已拒绝(rejected): 意味着操作失败,返回一个私有的值reason
    const PENDING = "Pending"; //等待
    const FULLFILLED = "Fullfilled"; //执行
    const REJECTED = "Rejected"; //拒绝
    class myPromise {
      constructor(excuter) {
        if (typeof excuter !== "function") {
          throw new Error("myPromise must accept a function");
        }
        this.state = PENDING;
        this.reason = undefined;
        this.value = undefined;
        try {
          excuter(this._resolve.bind(this), this._reject.bind(this));
        } catch (error) {
          this.reject(error);
        }
      }
      // Promise类还具有静态方法 resolve 避免重名
      _resolve(val) {
        if (this.state !== PENDING) return;
        this.state = FULLFILLED;
        this.value = val;
      }
    
      _reject(err) {
        if (this.state !== PENDING) return;
        this.state = REJECTED;
        this.reason = err;
      }
    }
    
    

    手写Promise记录

    最重要的then方法

    提供一个 then 方法来访问当前或最终的 value 或 reason,then方法需要满足:

    1. 接收两个函数作为参数,参数可选
    2. 当参数不为函数时会被忽略(2.2.1)
    3. 两个函数都是异步执行,会放入事件队列等待下一轮 tick
    4. 当调用 onFulfilled 函数时,会将当前 Promise 的 value 值作为参数传入。
    5. 当调用 onRejected 函数时,会将当前 Promise 的 reason 失败原因作为参数传入。
    6. onFulfilled 与 onRejected调用次数不可超过一次,状态改变前其不可被调用
    7. then 函数的返回值为 Promise,这是then可以链式调用的原因
    8. then 可以被同一个 Promise 多次调用

    参照上面这些规则,我们可以完善我们的myPromise,首先需要支持多次调用,这点可以用两个数组onFullfilledQueues,onRejectedQueues来维护,通过在then方法注册的时候将回调添加到队列中,等待状态改变执行。

     constructor(excuter) {
        if (typeof excuter !== "function") {
          throw new Error("myPromise must accept a function");
        }
        this.state = PENDING;
        this.reason = undefined;
        this.value = undefined;
    
        // 添加成功回调函数队列
        this.onFullfilledQueues = [];
    
        // 添加失败回调函数队列
        this.onRejectedQueues = [];
        try {
          excuter(this.resolve.bind(this), this.reject.bind(this));
        } catch (error) {
          this.reject(error);
        }
      }
    

    实现then方法,当一个 Promise 的状态被 fulfilled 之后,会执行其回调函数,而回调函数返回的结果会被当作 value,返回给下一个 Promise(也就是then 中产生的 Promise),同时下一个 Promise的状态也会被改变(执行 resolve 或 reject),然后再去执行其回调,以此类推下去... 下面参考promise解决程序(2.3)实现一个解决过程:

    // Promise 解决过程2.3
      resolvePromise(promise, x, resolve, reject) {
        // 2.3.1
        if (promise === x) {
          return reject(new TypeError("error found in: promise A+ 2.3.1"));
        }
        //2.3.2
        if (x instanceof myPromise) {
          if (x.state === FULLFILLED) {
            resolve(x.value);
          } else if (x.state === REJECTED) {
            reject(x.reason);
          } else {
            x.then((y) => {
              this.resolvePromise(promise, y, resolve, reject);
            }, reject);
          }
        } else if (//2.3.3
          x !== null &&
          (typeof x === "object" || typeof x === "function")
        ) {
          var executed;
          try {
            var then = x.then;
            if (typeof then === "function") {
              then.call(
                x,
                (y) => {
                  if (executed) return;
                  executed = true;
                  this.resolvePromise(promise, y, resolve, reject);
                },
                (e) => {
                  if (executed) return;
                  executed = true;
                  reject(e);
                }
              );
            } else {
              resolve(x);
            }
          } catch (e) {
            if (executed) return;
            executed = true;
            reject(e);
          }
        } else {
          resolve(x);
        }
      }
    

    根据上文中 then 方法的规则,我们知道返回的新的 Promise 对象的状态依赖于当前 then 方法回调函数执行的情况以及返回值,我们举几个场景说明:

    1. 如果 onFulfilled 或者 onRejected 返回一个值 x,
      • 若 x 不为 Promise ,则使 x 直接作为新返回的 Promise 对象的值 -- 见第四点
      • 若 x 为 Promise ,这时后一个回调函数,就会等待该 Promise 对象(即 x )的状态发生变化,才会被调用,并且新的 Promise 状态和 x 的状态相同。
    //1.非promise
    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, 1000)
    })
    promise2 = promise1.then(res => {
      // 返回一个普通值
      return '这里返回一个普通值'
    })
    promise2.then(res => {
      console.log(res) //1秒后打印出:这里返回一个普通值
    })
    
    //2. promise
    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, 1000)
    })
    promise2 = promise1.then(res => {
      // 返回一个Promise对象
      return new Promise((resolve, reject) => {
        setTimeout(() => {
         resolve('这里返回一个Promise')
        }, 2000)
      })
    })
    promise2.then(res => {
      console.log(res) //3秒后打印出:这里返回一个Promise
    })
    
    1. 如果 onFulfilled 或者onRejected 抛出一个异常 e ,则promise2必须变为失败(Rejected),并返回失败的值 e
    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, 1000)
    })
    promise2 = promise1.then(res => {
      // 返回一个Promise对象
      return new Promise((resolve, reject) => {
        setTimeout(() => {
         resolve('这里返回一个Promise')
        }, 2000)
      })
    })
    promise2.then(res => {
      console.log(res) //3秒后打印出:这里返回一个Promise
    })
    
    1. 如果onFulfilled 不是函数且 promise1 状态为成功(Fulfilled), promise2 必须变为成功(Fulfilled)并返回 promise1 成功的值 -- 规范第二点,当参数不为函数时会被忽略
    2. 如果 onRejected 不是函数且 promise1 状态为失败(Rejected),promise2必须变为失败(Rejected) 并返回 promise1 失败的值

    根据上述规则以及解决过程完善then方法

    //实现Promise的then方法
      then(onFullfilled, onRejected) {
        onFullfilled =
          typeof onFullfilled === "function"
            ? onFullfilled
            : function (x) {
                return x;
              };
        onRejected =
          typeof onRejected === "function"
            ? onRejected
            : function (e) {
                throw e;
              };
        // 返回一个新的Promise对象
        let promise = new myPromise((resolve, reject) => {
          switch (this.state) {
            case PENDING:
              this.onFullfilledQueues.push(() => {
                try {
                  var x = onFullfilled(this.value);
                  this.resolvePromise(promise, x, resolve, reject);
                } catch (error) {
                  reject(error);
                }
              });
              this.onRejectedQueues.push(() => {
                try {
                  var x = onRejected(this.reason);
                  this.resolvePromise(promise, x, resolve, reject);
                } catch (error) {
                  reject(error);
                }
              });
              break;
            case FULLFILLED:
              try {
                var x = onFullfilled(this.value);
                this.resolvePromise(promise, x, resolve, reject);
              } catch (error) {
                reject(error);
              }
              break;
            case REJECTED:
              try {
                var x = onRejected(this.reason);
                this.resolvePromise(promise, x, resolve, reject);
              } catch (error) {
                reject(error);
              }
              break;
          }
        });
    
        return promise;
      }
    

    分析一下代码的执行:

    1. 实例化promsie的过程实例上会执行传入的函数,将类内部的resolve和reject 绑到形参
    2. 遇到then方法会将参数存到这个实例的执行队列上(之前的回调函数),then 中返回了新的 Promise,但是then中注册的回调仍然是属于上一个 Promise 的,then 方法可被同一个 promise 调用多次
    3. 明确一点: promise的状态只由他内部的resolve reject中改变
    4. Promise里的关键是要保证,then方法传入的参数 onFulfilled 和 onRejected,必须在then方法被调用的那一轮事件循环之后的新执行栈中执行
    5. 当promise状态改变的时候 会执行队列里的函数 然后根据现在的状态决定下一个promise的状态(then返回那个),我们把then方法返回新promise的参数(resolve,reject)传给了解决过程resolvePromise,这样我们就可改变这个新promise的状态。这就是链式调用的原理。

    catch方法

    其实就是只处理错误状态的then方法

    // 添加catch方法
    catch (onRejected) {
      return this.then(undefined, onRejected)
    }
    

    还有其他静态方法,先占个坑

    测试工具 promises-tests

    使用方法:

    1. git clone
    2. 在文件中导出出测试的接口
    3. 修改测试命令 在package.json中测试自己的promise.js文件(根据自己的文件而定) "test": "promises-aplus-tests promise.js

    跑完测试需要一点时间,此工具用例没有测试到resolve()中传入promsie的情况,我在童欧巴大佬的1.手写 Promise 全家桶讨论过这个issue,大佬的文章质量都很高,大家可以follow学习。 传送: 大佬github 手写Promise记录

    完整代码

    const PENDING = "Pending"; //等待
    const FULLFILLED = "Fullfilled"; //执行
    const REJECTED = "Rejected"; //拒绝
    
    class myPromise {
      constructor(excuter) {
        if (typeof excuter !== "function") {
          throw new Error("myPromise must accept a function");
        }
        this.state = PENDING;
        this.reason = undefined;
        this.value = undefined;
    
        // 添加成功回调函数队列
        this.onFullfilledQueues = [];
    
        // 添加失败回调函数队列
        this.onRejectedQueues = [];
        try {
          excuter(this._resolve.bind(this), this._reject.bind(this));
        } catch (error) {
          this._reject(error);
        }
      }
    
      //需要异步调用数组中的函数,这里使用 setTimeout 来模拟异步。
      _resolve(val) {
        // console.log(val);
        //2.3. Promise解决程序
        if (val instanceof myPromise) {
          return val.then(this._resolve.bind(this), this._reject.bind(this));
          //   return val.then(this._resolve, this._reject);
        }
    
        const run = () => {
          if (this.state !== PENDING) return;
          this.state = FULLFILLED;
          this.value = val;
          // 依次执行成功队列中的函数,并清空队列
          let cb;
          while ((cb = this.onFullfilledQueues.shift())) {
            cb(this.value);
          }
        };
    
        // 为了支持同步的Promise,这里采用异步调用
        setTimeout(run);
      }
    
      _reject(err) {
        const run = () => {
          if (this.state !== PENDING) return;
          this.state = REJECTED;
          this.reason = err;
          // 依次执行失败队列中的函数,并清空队列
          let cb;
          while ((cb = this.onRejectedQueues.shift())) {
            cb(err);
          }
        };
        setTimeout(run);
      }
    
      // Promise 解决过程2.3
      resolvePromise(promise, x, resolve, reject) {
        // console.log(promise, x);
        if (promise === x) {
          return reject(new TypeError("error found in: promise A+ 2.3.1"));
        }
        if (x instanceof myPromise) {
          if (x.state === FULLFILLED) {
            resolve(x.value);
          } else if (x.state === REJECTED) {
            reject(x.reason);
          } else {
            x.then((y) => {
              this.resolvePromise(promise, y, resolve, reject);
            }, reject);
          }
        } else if (
          x !== null &&
          (typeof x === "object" || typeof x === "function")
        ) {
          var executed;
          try {
            var then = x.then;
            if (typeof then === "function") {
              then.call(
                x,
                (y) => {
                  if (executed) return;
                  executed = true;
                  this.resolvePromise(promise, y, resolve, reject);
                },
                (e) => {
                  if (executed) return;
                  executed = true;
                  reject(e);
                }
              );
            } else {
              resolve(x);
            }
          } catch (e) {
            if (executed) return;
            executed = true;
            reject(e);
          }
        } else {
          resolve(x);
        }
      }
    
      //实现Promise的then方法
      then(onFullfilled, onRejected) {
        onFullfilled =
          typeof onFullfilled === "function"
            ? onFullfilled
            : function (x) {
                return x;
              };
        onRejected =
          typeof onRejected === "function"
            ? onRejected
            : function (e) {
                throw e;
              };
        // 返回一个新的Promise对象
        let promise = new myPromise((resolve, reject) => {
          switch (this.state) {
            case PENDING:
              this.onFullfilledQueues.push(() => {
                try {
                  var x = onFullfilled(this.value);
                  this.resolvePromise(promise, x, resolve, reject);
                } catch (error) {
                  reject(error);
                }
              });
              this.onRejectedQueues.push(() => {
                try {
                  var x = onRejected(this.reason);
                  this.resolvePromise(promise, x, resolve, reject);
                } catch (error) {
                  reject(error);
                }
              });
              break;
            case FULLFILLED:
              try {
                var x = onFullfilled(this.value);
                this.resolvePromise(promise, x, resolve, reject);
              } catch (error) {
                reject(error);
              }
              break;
            case REJECTED:
              try {
                var x = onRejected(this.reason);
                this.resolvePromise(promise, x, resolve, reject);
              } catch (error) {
                reject(error);
              }
              break;
          }
        });
    
        return promise;
      }
    }
    
    

    后记

    代码中有疑问或者不对的地方欢迎各位批评指正,共同进步。求点赞三连QAQ


    起源地下载网 » 手写Promise记录

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元