带你实现一个Promise

前言

此篇文章主要是用来加深本身对promise原理的理解。es6

什么是Promise

Promise是JS解决异步编程的一种方式,是ES6推广的一个新概念。所谓的Promise至关于一个状态机,里面承载着将来某一个时刻所发生的状态。编程

Promise的状态

Promise的有三种状态:数组

  • pending: 一个promise在resolve或者reject前就处于这个状态。
  • fulfilled: 一个promise被resolve后就处于fulfilled状态,这个状态不能再改变,并且必须拥有一个不可变的值(value)。
  • rejected: 一个promise被reject后就处于rejected状态,这个状态也不能再改变,并且必须拥有一个不可变的拒绝缘由(reason)。

Promise的特色是什么

  • 对象的状态不受外界的影响,只有异步结果才能决定对象的状态
  • 对象的状态一旦发生改变,就不会再改变了。任什么时候刻均可以拿到此结果

Promise解决了什么问题

Promise和回调函数是2个概念,Promise是新的概念而不是回调函数的扩展。promise

  • 使用Promise解决了回调地狱的问题markdown

    Promise经过链式调用解决了回调函数层层嵌套的问题,异步

  • 使用Promise处理异步编程,不会使得代码逻辑跳跃异步编程

    最原始的回调函数处理异步编程使得代码跳跃,由于当异步有告终果才会去执行回调函数,那么callback的执行和定义可能并不在同一个地方。使得代码的可读性没那么清晰。而Promsie经过.then函数调用就能够解决这样的逻辑跳跃问题。函数

  • 使用Promsie能够捕获处理错误信息oop

    最原始的回调函数处理异步编程没法实现错误信息的捕获,那么经过使用Promise的.catch方法或者在.then的第二个参数(回调函数)来处理错误信息。测试

Promsie实现思路分析

在实现Promsie的过程中,须要先了解Promises/A+规范,这里可查看Promises/A+规范翻译

Promise的基本结构

let p=new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('FULFILLED');
  }, 1000)
}).then(res=>{
	console.log(res);
}).catch(err=>{
	console.log(err);
})
复制代码

如上是使用ES6中Promise的基本结构,可见Promise是一个构造函数或者是一个类,传入一个回调函数,回调函数接收resolve和reject两个方法;而且Promise有.then和.catch基本这些基本的方法。

then方法

promise.then(onFulfilled, onRejected)
复制代码

一个Promise必须拥有then方法,并且then方法接收2个可选参数onFulfilled和onRejected。

  • onFulfilled 用来当状态值是Fulfilled接受的处理函数。
  • onRejected 用来处理状态值是Rejected拒绝的处理函数。
  • then 方法能够被同一个 promise 调用屡次
    • 当 promise 成功执行时,全部 onFulfilled 需按照其注册顺序依次回调
    • 当 promise 被拒绝执行时,全部的 onRejected 需按照其注册顺序依次回调
  • then 方法返回一个 promise 对象。

catch方法

一个Promise必须拥有catch方法,用来处理拒绝的状况。

promise.catch(onRejected)
复制代码

Promise的状态值分析

Promise有三个状态值,因此先定义三个状态常量。一个Promise的当前状态必须是等待状态(Pending), 接受状态(Fulfilled) 和 拒绝状态(Rejected)当中的一种。

// 先定义三个常量表示状态
  const PENDING = "pending";
  const FULFILLED = "fulfilled";
  const REJECTED = "rejected";
复制代码
  • 当Promsie的状态值是pending时,那么它有可能变成Fulfilled或者是Rejected。
  • 当Promise的状态是Fulfilled或者是Rejected,那么就没法改变它的值了。
  • 当状态值为rejected时,有一个不可变的拒绝缘由。

Promise封装

MyPromise接收一个fn(函数),执行这个fn,而且将resolve和reject传递出去。

  • 当调用resolve时将状态值PENDING变成FULFILLED,而且接收value值(成功的值)
  • 当调用reject时将状态值PENDING变成REJECTED,而且接收reason值(失败拒绝的值)

MyPromise经过使用try--catch来捕获错误信息。

function MyPromise(fn) {
    this.status = PENDING; // 初始状态为pending
    this.value = null; // 初始化value
    this.reason = null; // 初始化reason
    // 存一下this,以便resolve和reject里面访问
    var that = this;
    // resolve方法参数是value
    function resolve(value) {
        if (that.status === PENDING) {
            that.status = FULFILLED;
            that.value = value;
        }
    }

    // reject方法参数是reason
    function reject(reason) {
        if (that.status === PENDING) {
            that.status = REJECTED;
            that.reason = reason;
        }
    }
    try {
        fn(resolve, reject);
    } catch (error) {
        reject(error);
    }
}
复制代码

接下来实现一个then函数:

根据上面Promises/A+规范的分析可见,then方法返回一个 promise ,而且then方法能够处理不一样状态值的状况。想一想当咱们使用promise时,若是成功以后就会调用then的onFulfilled方法,当失败以后就会调用then的onRejected方法。因此,在大致上能肯定的是有条件分支是当状态值为FULFILLED和REJECTED这两种状况,在想一想状态值不一样的时候,是如何调用onFulfilled方法和onRejected方法。

在调用onFulfilled方法和onRejected方法以前会去判断onFulfilled方法和onRejected方法是否是一个函数,若是是才调用这个函数,不是则直接返回值。(值是promise1的值是根据成功或者失败决定返回reason仍是value)。其实就是至关于将promise1的值穿透到promise2。

那若是onFulfilled方法和onRejected方法是函数而且有本身的返回值,那么咱们就要根据Promises/A+规范进行编码处理。

在将Promises/A+规范转换成代码以前,咱们先看看以下的代码:

new Promise(fn).then(onFulfilled, onRejected);
复制代码

当咱们以如上代码使用promise时,Promise的状态值仍是 PENDING (then方法和Promise构造函数一块儿执行,根本不知道何时状态值发生改变)。这时候确定不能当即调onFulfilled或者onRejected的,由于fn到底成功仍是失败还不知道。那何时知道fn成功仍是失败呢?答案是fn里面主动调resolve或者reject的时候。因此要先收集onFulfilled和onRejected方法(确定在then方法里面去收集);若是状态值仍是PENDING,应该将onFulfilled和onRejected两个回调存起来,等到fn有告终论,resolve或者reject的时候再来调用对应的代码(确定在状态值发生变化的地方去调用函数)。由于后面then还有链式调用,会有多个onFulfilled和onRejected,这里用两个数组将他们存起来,等resolve或者reject的时候将数组里面的所有方法拿出来执行一遍。

这个过程像极了订阅发布模式。那这样的话then方法里面确定须要多一条状态值为PENDING的条件分支。

因此新增MyPromise函数的代码以下:

function MyPromise(fn) {
    ...
    // 构造函数里面添加两个数组存储成功和失败的回调
    + this.onFulfilledCallbacks = [];
    + this.onRejectedCallbacks = [];

    // 存一下this,以便resolve和reject里面访问
    var that = this;
    // resolve方法参数是value
    function resolve(value) {
        if (that.status === PENDING) {
            ...
            // resolve里面将全部成功的回调拿出来执行
           + that.onFulfilledCallbacks.forEach((callback) => {
           +    callback(that.value);
           + });
        }
    }

    // reject方法参数是reason
    function reject(reason) {
        if (that.status === PENDING) {
         	...
            // resolve里面将全部失败的回调拿出来执行
           + that.onRejectedCallbacks.forEach((callback) => {
           +     callback(that.reason);
           + });
        }
    }
	...
}
复制代码

回到当onFulfilled方法和onRejected方法是函数而且有本身的返回值时的状况,这时候就须要关注Promises/A+规范的Promise 解决过程,将 Promise 解决过程转成代码以下:

function resolvePromise(promise, x, resolve, reject) {
  // 若是 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
  // 这是为了防止死循环
  if (promise === x) {
      return reject(
          new TypeError("The promise and the return value are the same")
      );
  }

  if (x instanceof MyPromise) {
      // 若是 x 为 Promise ,则使 promise 接受 x 的状态
      // 也就是继续执行x,若是执行的时候拿到一个y,还要继续解析y
      // 这个if跟下面判断then而后拿到执行其实重复了,无关紧要
      x.then(function(y) {
          resolvePromise(promise, y, resolve, reject);
      }, reject);
  }
  // 若是 x 为对象或者函数
  else if (typeof x === "object" || typeof x === "function") {
      // 这个坑是跑测试的时候发现的,若是x是null,应该直接resolve
      if (x === null) {
          return resolve(x);
      }

      try {
          // 把 x.then 赋值给 then
          var then = x.then;
      } catch (error) {
          // 若是取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
          return reject(error);
      }

      // 若是 then 是函数
      if (typeof then === "function") {
          var called = false;
          // 将 x 做为函数的做用域 this 调用之
          // 传递两个回调函数做为参数,第一个参数叫作 resolvePromise ,第二个参数叫作 rejectPromise
          // 名字重名了,我直接用匿名函数了
          try {
              then.call(
                  x,
                  // 若是 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
                  function(y) {
                      // 若是 resolvePromise 和 rejectPromise 均被调用,
                      // 或者被同一参数调用了屡次,则优先采用首次调用并忽略剩下的调用
                      // 实现这条须要前面加一个变量called
                      if (called) return;
                      called = true;
                      resolvePromise(promise, y, resolve, reject);
                  },
                  // 若是 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                  function(r) {
                      if (called) return;
                      called = true;
                      reject(r);
                  }
              );
          } catch (error) {
              // 若是调用 then 方法抛出了异常 e:
              // 若是 resolvePromise 或 rejectPromise 已经被调用,则忽略之
              if (called) return;

              // 不然以 e 为据因拒绝 promise
              reject(error);
          }
      } else {
          // 若是 then 不是函数,以 x 为参数执行 promise
          resolve(x);
      }
  } else {
      // 若是 x 不为对象或者函数,以 x 为参数执行 promise
      resolve(x);
  }
}
复制代码

在完成Promise 解决过程以后,咱们还需讨论onFulfilled 和 onRejected 方法在如何执行?是同步执行仍是异步执行呢?

在规范中讲到:实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环以后的新执行栈中执行。

因此在咱们执行onFulfilled 和 onRejected的时候都应该包到setTimeout里面去。那么then 方法的实现代码以下:

MyPromise.prototype.then = function(onFulfilled, onRejected) {
      // 若是onFulfilled不是函数,给一个默认函数,返回value
      var realOnFulfilled = onFulfilled;
      // 若是onRejected不是函数,给一个默认函数,返回reason的Error
      var realOnRejected = onRejected;
      var that = this; // 保存一下this
      if (this.status === FULFILLED) {
          var promise2 = new MyPromise(function(resolve, reject) {
              setTimeout(function() {
                  try {
                      if (typeof onFulfilled !== "function") {
                          resolve(that.value);
                      } else {
                          var x = realOnFulfilled(that.value);
                          resolvePromise(promise2, x, resolve, reject);
                      }
                  } catch (error) {
                      reject(error);
                  }
              }, 0);
          });
          return promise2;
      }

      if (this.status === REJECTED) {
          var promise2 = new MyPromise(function(resolve, reject) {
              setTimeout(function() {
                  try {
                      if (typeof onRejected !== "function") {
                          reject(that.reason);
                      } else {
                          var x = realOnRejected(that.reason);
                          resolvePromise(promise2, x, resolve, reject);
                      }
                  } catch (error) {
                      reject(error);
                  }
              }, 0);
          });
          return promise2;
      }

      // 若是仍是PENDING状态,将回调保存下来
      if (this.status === PENDING) {
          var promise2 = new MyPromise(function(resolve, reject) {
              that.onFulfilledCallbacks.push(function() {
                  setTimeout(function() {
                      try {
                          if (typeof onFulfilled !== "function") {
                              resolve(that.value);
                          } else {
                              var x = realOnFulfilled(that.value);
                              resolvePromise(promise2, x, resolve, reject);
                          }
                      } catch (error) {
                          reject(error);
                      }
                  }, 0);
              });
              that.onRejectedCallbacks.push(function() {
                  setTimeout(function() {
                      try {
                          if (typeof onRejected !== "function") {
                              reject(that.reason);
                          } else {
                              var x = realOnRejected(that.reason);
                              resolvePromise(promise2, x, resolve, reject);
                          }
                      } catch (error) {
                          reject(error);
                      }
                  }, 0);
              });
          });

          return promise2;
      }
};
复制代码

其余Promise方法

根据ES6的文档Promise 对象的用法手写Promise的其余方法,这里先拎出来各方法的特色再编写代码。

1.MyPromise.resolve特色

  • 参数是一个 Promise 实例则返回该实例
  • 参数不是Promise 实例则包装成Promise对象而且直接resolve参数
MyPromise.resolve = function(parameter) {
    if (parameter instanceof MyPromise) {
        return parameter;
    }

    return new MyPromise(function(resolve) {
        resolve(parameter);
    });
};
复制代码
  1. MyPromise.reject特色
  • 返回一个新的 Promise 实例,该实例的状态为reject,而且据由于传入的参数
MyPromise.reject = function(reason) {
    return new MyPromise(function(resolve, reject) {
        reject(reason);
    });
};

复制代码

3.MyPromise.all特色

  • 传入一个数组做为参数,数组的每个元素都是一个promise实例
  • 返回一个新的promise,而且promise的结果值是一个数组,数组中的值为参数中每个promise的返回值而且一一对应的关系
  • 只要参数中的promise实例一个发生错误了,那么这个新的promise状态值就是拒绝状态
MyPromise.all = function(args=[]) {
    let count=0;
    let callbackArr=[];
    return new Promise((resolve,reject)=>{
      for(let i=0;i<args.length;i++){
       // (function(i){
          args[i].then(res=>{
            callbackArr[i]=res;
            count++;
            if(count==args.length){
              return resolve(callbackArr);
            }
          }).catch(err=>{
            return reject(err);
          })
        //})(i)
      }
    })
};

复制代码

MyPromise.prototype.catch 特色

  • .catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
MyPromise.prototype.catch = function(onRejected) {
     this.then(null, onRejected);
 };

复制代码

其余的方法小伙伴自行实现,抓住特色,我以为实现其余就不会很难了。

总结

知原理知天下,当会使用一个语法的时候多考虑背后的实现思想原理,会加深对此语法的理解。

相关文章
相关标签/搜索