promise内部实现

Posted mcgee0731

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了promise内部实现相关的知识,希望对你有一定的参考价值。

从本文你将了解到

  • 什么是promise
  • promise的内部实现

    • resolve实例属性
    • reject实例属性
    • then方法
    • then方法的多次调用
    • then的链式调用
    • 错误捕获try{}catch(e){}
    • then可选参数
    • 静态方法 all 的实现
    • 静态方法 resolve 的实现
    • 实例方法 finally 的实现
    • 实例方法 catch 的实现

      什么是promise

      步骤分析

      /**
       * 1.Promise是个类,参数是个回调函数(执行器),这个执行器会立即执行
       * 2.promise中有三种状态,成功fulfilled,失败rejected,等待pending,一旦状态确定,则不可更改
       *      pending -> fulfilled
       *      pending -> rejected
       * 3.resolve,reject函数是用来改变状态的
       *      resolve()  pending -> fulfilled
       *      reject()   pending -> rejected
       * 4.then方法内部做的事情就是判断状态 如果状态是成功调用成功的回调函数,如果是失败调用失败的回调函数,
       *   then方法是被定义在原型对象中的
       * 5.then成功的回调函数有个参数,表示成功之后的值。then失败的回调函数有个参数,表示失败之后的原因
      */
      new Promise((resolve,reject)=>{
      resolve("成功")
      // reject("失败")
      })

实现

实现最基础的功能

声明一个 MyPromise 类,在构造函数中接收一个执行器,并立即执行这个执行器

// 定义状态常量
const PENDING = \'pending\';     // 等待
const FULFILLED = \'fulfilled\'; // 成功
const REJECTED = \'rejected\';   // 失败

class MyPromise {
  constructor(executor) {
    // 执行器接收两个参数,resolve 和 reject
    // 执行器立即执行
    executor(this.resolve, this.reject);
  }
  // 定义实例的状态属性: 初始值为 pending,状态一旦确定,就不可更改  
  status = PENDING;
}

执行 执行器 的时候,需要传递两个参数,resolve 和 reject。

  • 在 执行器 函数体中执行这两个方法的时候是直接调用的 resolve(); reject()
  • 直接执行函数的话,这个函数体内部的 this 会指向 window。
  • 所以在定义 resolve 和 reject方法的时候使用箭头函数,避免 this 指向的问题,使其能够指向 promise 这个实例。
  • 因为在 执行器 函数体中,resolve 和 reject 是用来更改 Promise 的状态的,而且更改之后,不可以再次更改
class MyPromise {
  // 构造函数执行器
  ...

  resolve = () => {
    // 状态不是 pending 的时候,return。
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
  }
  reject = () => {
    // 状态不是 pending 的时候,return。
    if (this.status !== PENDING) return;
    this.status = REJECTED;
  }
}

调用 resolvereject 的时候传入了参数:
resolve(\'成功后的数据\')
reject(\'失败的原因\')
所以在 resolvereject 中接收对应的参数,并保存到实例属性中

class MyPromise {
  // 构造函数执行器
  ...
  // 定义两个属性,
  // 成功之后的值
  value = undefined
  // 失败的原因
  reason = undefined

  resolve = value => { // 接收成功的数据
    // 状态不是 pending 的时候,return。
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    // 保存成功的值
    this.value = value;
  }
  reject = reason => { // 接收失败的原因
    // 状态不是 pending 的时候,return。
    if (this.status !== PENDING) return;
    this.status = REJECTED;
    // 保存失败的原因
    this.reason = reason;
  }
}

在调用 Promise() 之后,会返回一个 promise 实例,这个实例中有一个 then 方法,用于处理函数内结束的成功或失败的回调

  • then 有两个参数,一个参数是成功的回调successCallback,一个是失败的回调failCallback
  • 在调用 successCallback 的时候,需要将 成功的数据/值 传递进去
  • 在调用 failCallback 的时候,需要将 错误的原因 传递进去
  • 成功的值 和 失败的原因已经用定义的变量 valuereason 保存过了。

要调用成功的回调还是失败的回调,需要要看 Promise 的状态:

如果是 Fulfilled 调用成功的回调;如果是 Rejected 调用失败的回调;

class MyPromise{
  ...
  then(successCallback, failCallback) {
    if (this.status === FULFILLED) {
      successCallback(this.value);
    } else if (this.status === REJECTED) {
      failCallback(this.reason);
    }
  }
}

加入 异步逻辑

如果在使用 Promise 的时候,把 resolvereject 放在异步逻辑中。

// 2s 后再执行成功的 resolve
setTimeout(() => {
resolve(\'成功\')
}, 2000)

这个时候,再使用 then 的话,then 里面判断 promise 状态,此时的 status 还是 padding

因为 promise 的状态 status 是靠调用 resolve/reject 来更改的。延迟了 resolve/reject 的调用,就是延迟了状态的更新。

而 then 方法中只判断了 成功和失败 的状态,并没有去判断 等待的状态(也是就异步的状态)

  then(successCallback, failCallback) {
    if (this.status === FULFILLED) {
      successCallback(this.value);
    } else if (this.status === REJECTED) {
      failCallback(this.reason);
    }
  }

所以我们要做的事情是:如果在调用 then 的时候,promise 的状态是 pending等待状态,需要将 传递进来的成功的回调和失败的回调 存储起来,以便延迟结束调用。

class MyPromise {
  ...
  // 成功回调
  successCallback = undefined;
  // 失败回调
  failCallback = undefined;

  ...

  then(successCallback, failCallback) {
    if (this.status === FULFILLED) {
      successCallback(this.value);
    } else if (this.status === REJECTED) {
      failCallback(this.reason);
    } else {
      // 等待
      // 保存成功 和 失败的回调函数
      this.successCallback = successCallback;
      this.failCallback = failCallback;
    }
  }
}

之后在异步逻辑结束之后,会调用 resolve 或 reject ,此时就可以在Promise中的 resolve 或 reject 方法里判断是否有对应的回调,如果有就执行它

resolve = value => {
  ...
  // 判断是否有成功的回调
  this.successCallback && this.successCallback(this.value)
}
reject = reason => {
  ...
  // 判断是否有失败的回调
  this.failCallback && this.failCallback(this.reason);
}

实现 then 方法多次调用 添加多个处理函数

  • 每个 Promise 对象的 then 方法都是可以被多次调用的,
  • 当 then 方法被调用的时候,每个 then 方法里面的回调函数都是要被调用的
  • 如果多次调用了 then 方法,应该先将所有 then 方法里面的回调函数都存储起来,当 Promise 状态发生变化的时候,将依次执行这些回调函数
  • 为了存储多个 then 里面的回调函数,需要将 successCallbackfailCallback 属性改为数组

    // 成功回调
    // successCallback = undefined;
    successCallback = [];
    // 失败回调
    // failCallback = undefined;
    failCallback = [];
    
    then() {
    ...
      // 保存成功 和 失败的回调函数
      // this.successCallback = successCallback;
      // this.failCallback = failCallback;
      this.successCallback.push(successCallback);
      this.failCallback.push(failCallback);
    ...
    }

    然后在 Promise 状态更新的时候,依次调用这些回调函数

    resolve = value => {
      ...
      // 如果成功回调存在,就执行它
      // this.successCallback && this.successCallback(this.value);
      while (this.successCallback.length) {
        // 弹出第一个回调函数,并执行
        this.successCallback.shift()(this.value);
      }
    }
    reject = reason => {
      ...
      // 如果失败回调存在,就执行它
      // this.failCallback && this.failCallback(this.reason);
      while (this.failCallback.length) {
        // 弹出第一个回调函数,并执行
        this.failCallback.shift()(this.reason);
      }
    }

    使用

    promise.then(value => {
    console.log(1);
    console.log(value);
    });
    promise.then(value => {
    console.log(2);
    console.log(value);
    });
    promise.then(value => {
    console.log(3);
    console.log(value);
    });

    结果

    1
    成功
    2
    成功
    3
    成功

    then 方法的链式调用

  • Promise 的 then 方法是可以被链式调用的
  • 每一个 then 方法中回调函数拿到的值,其实是上一个 then 方法回调函数的返回值
// test.js

promise
  .then(value => {
    console.log(value);
    return 100;
  })
  .then(value => { // 这一个value 就是前面 then 方法回调函数中 return 的100
    console.log(value); // 100
  });
  1. 实现 then 方法的链式调用

每一个 then 方法都会返回一个 Promise 对象

  then(successCallback, failCallback) {
    // 实现 then 方法返回一个 Promise 对象
    let promise2 = new MyPromise(() => {
      if (this.status === FULFILLED) {
        successCallback(this.value);
      } else if (this.status === REJECTED) {
        failCallback(this.reason);
      } else {
        // 等待
        // 保存成功 和 失败的回调函数
        // this.successCallback = successCallback;
        // this.failCallback = failCallback;
        this.successCallback.push(successCallback);
        this.failCallback.push(failCallback);
      }
    });
    // 返回 Promise 对象
    return promise2;
  }
  1. 将上一个 then 方法回调函数的返回值传递给下一个 then 方法回调函数
  • 想要下一个then接收到上一个then,那么上一个then要返回一个prommise对象
  • 获取回调函数的返回值

    // 成功的回调
    then(successCallback,failCallback){
    ...
      let promise2 = new MyPromise(() => {
          if (this.status === FULFILLED) {
              // 保存成功回调的返回值
              let x = successCallback(this.value);
          }
    ...
      });
      return promise2
    }

    将 x 传递给下一个 then。下一个 then 其实就是 promise2 里面的 then,(因为你返回了个promise2,他后面接的then肯定是promise2的呀)

只要调用 promise2 的 resolve方法,那么promise2的then就会执行,同时要传递的 x 通过resolve(x) 就会传递给下一个 then 的回调函数了

// 成功的回调
then(successCallback,failCallback){
  // 只要调用 resolve 方法,就能将 x 传递给 then 方法的回调函数
  let promise2 = new MyPromise((resolve, reject) => {
    let x = successCallback(this.value);
    resolve(x);
  })
}

then 方法链式调用返回值的判断

  • 如果上一个 then 方法回调函数的返回值是一个 普通值,直接 resolve;
  • 如果上一个 then 方法回调函数的返回值是一个 Promise 对象,则需要判断这个 Promise 对象的返回结果(状态),并根据状态来决定要使用 resolve 还是 reject
// 成功的回调
then(successCallback,failCallback){
  let promise2 = new MyPromise((resolve, reject) => {
    let x = successCallback(this.value);
    // 判断 x 的值是普通值还是 Promise 对象,
    // 如果是普通值,直接调用 resolve
    // 如果是 Promise 对象,查看这个 Promise 对象的返回结果,
    // 再根据返回结果来决定调用 resolve 还是 reject
    resolveRromise(x, resolve, reject);
  })
}

function resolveRromise(x, resolve, reject) {
  // 使用 instanceof 来判断 x 是否是 MyPromise 的实例对象
  if (x instanceof MyPromise) {
    // 调用 then 方法来查看 x 的返回值,
    // 如果是成功的,就调用then的第一个参数方法,失败则调用then的第二个参数方法
    // 简写成执行promise2对象的resolve和reject方法,向下传递
    // x.then(value => resolve(value), reason => reject(reason))
    x.then(resolve, reject);
  } else {
    resolve(x)
  }
}

测试

function other (){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log("other")
        },2000)
    })
}

promise.then(value=>{
    console.log(value)
    return other()  //返回一个promise
}).then(value=>{
    console.log(value) //输出other
})

then 方法链式调用识别 Promise 对象自返回

如果 then 方法的回调函数中,返回了自己(Promise对象),Promise 就会进入循环调用,这种情况是不被允许的:Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

var promise1 = new Promise((resolve, reject) => {
  resolve(100);
});

var promise2 = promise1.then(value => {
  console.log(value); // 100

  // 自己返回自己
  return promise2;
})

解决方法:判断当前 then 的值 promise2 对象 和 它内部返回给下一个then 的promise对象的返回值 x,是否相同,如果相同,就是 自反回。

...
then(){
    //传入promise2 在resolvePromise中将promise2和x进行判断
    resolvePromise(promise2, x, resolve, reject);
}
...

function resolvePromise(promise2, x, resolve, reject) {
  // 判断是否是 自反回
  if (promise2 === x) {
    return reject(new TypeError(\'Chaining cycle detected for promise #<Promise>\'))
  }
  // 使用 instanceof 来判断 x 是否是 MyPromise 对象
  if (x instanceof MyPromise) {
    // 调用 then 方法来查看 x 的返回值,
    // 如果是成功的,就调用resolve;失败就调用 reject
    // x.then(value => resolve(value), reason => reject(reason))
    x.then(resolve, reject);
  } else {
    resolve(x)
  }
}

问题:promise2 是new MyPromise((resolve, reject) => {...}) 执行完成之后才有的,而现在是在 new promise2 执行的过程中去获取它,是获取不到的。

解决方法就是将 取到回调和比较 promise2 和 x 的代码变为 异步执行。他会在then方法的所有同步代码执行完之后才执行,以便于取到promise2

// 将代码改为异步执行,确保拿到 promise2
setTimeout(() => {
  // 保存成功回调的返回值
  let x = successCallback(this.value);
  // 判断 x 的值是普通值还是 Promise 对象,
  // 如果是普通值,直接调用 resolve
  // 如果是 Promise 对象,查看这个 Promise 对象的返回结果,
  // 再根据返回结果来决定调用 resolve 还是 reject
  // resolve(x);
  resolveRromise(promise2, x, resolve, reject);
}, 0);

捕获错误及 then 链式调用其他状态代码补充

  1. 捕获执行器的错误

在执行执行器的时候,使用 try/catch 捕获异常

  • 当传入的执行器是个error不是正常的执行器时候获取e.message

    class MyPromise {
    constructor(executor) {
      try {
        executor(this.resolve, this.reject);
      } catch (err) {
        this.reject(err);
      }
    }
    }
  • 捕获 then 方法回调函数执行异常
  • 如果 then 方法的回调函数内发生异常,或者在then的回调函数内throw new Error
  • 则需要 reject 传递给下一个 then 方法的错误处理回调函数

    try {
    // 保存成功回调的返回值
    let x = successCallback(this.value);
    // 判断 x 的值是普通值还是 Promise 对象,
    // 如果是普通值,直接调用 resolve
    // 如果是 Promise 对象,查看这个 Promise 对象的返回结果,
    // 再根据返回结果来决定调用 resolve 还是 reject
    // resolve(x);
    resolveRromise(promise2, x, resolve, reject);
    } catch (err) {
    // 将异常传递给下一个 then 方法的错误处理回调函数,
    reject(err);
    }
  • 处理 catch 链式调用

前面处理了 resolve 的链式调用情况。而 reject 的处理同理。

...
else if (this.status === REJECTED) {
  // failCallback(this.reason);
  // 将代码改为异步执行,确保能够在 new Promise 执行完之后拿到 promise2
  setTimeout(() => {
    try {
      // 保存失败回调的返回值
      let x = failCallback(this.reason);
      // 判断 x 的值是普通值还是 Promise 对象,
      // 如果是普通值,直接调用 resolve
      // 如果是 Promise 对象,查看这个 Promise 对象的返回结果,
      // 再根据返回结果来决定调用 resolve 还是 reject
      // resolve(x);
      resolveRromise(promise2, x, resolve, reject);
    } catch (err) {
      // 将异常传递给下一个 then 方法的错误处理回调函数,
      reject(err);
    }
  }, 0);
}
...

!!!注意:

如果在错误处理回调函数中返回了一个正常值(比如: 10000),它就会进入下一个 then 方法的成功处理回调函数。

如果返回的是失败的,它就会进入下一个 then 的错误处理回调函数中

const promise = new MyPromise((resolve, reject) => {
  reject(\'失败\');
});

promise
  .then(value => {
    console.log(value);
    return 100;
  }, reason => {
    console.log(reason); // => 第一个then接收到失败,但是返回了个成功10000
    return 10000;
  })
  .then(value => {
    console.log(value); // => 10000
  }, reason => {
    console.log(reason);
  });
  1. 处理 异步逻辑 的链式调用,并捕获运行异常
...

else {
  // 等待
  // 保存成功 和 失败的回调函数
  // this.successCallback = successCallback;
  // this.failCallback = failCallback;

  // this.successCallback.push(successCallback);
  // this.failCallback.push(failCallback);

  // 保存一个函数,这个函数里面执行 成功/失败的回调
  this.successCallback.push(() => {
    setTimeout(() => {
      try {
        // 保存成功回调的返回值
        let x = successCallback(this.value);
        // 判断 x 的值是普通值还是 Promise 对象,
        // 如果是普通值,直接调用 resolve
        // 如果是 Promise 对象,查看这个 Promise 对象的返回结果,
        // 再根据返回结果来决定调用 resolve 还是 reject
        // resolve(x);
        resolveRromise(promise2, x, resolve, reject);
      } catch (err) {
        // 将异常传递给下一个 then 方法的错误处理回调函数,
        reject(err);
      }
    }, 0);
  });
  this.failCallback.push(() => {
    setTimeout(() => {
      try {
        // 保存失败回调的返回值
        let x = failCallback(this.reason);
        // 判断 x 的值是普通值还是 Promise 对象,
        // 如果是普通值,直接调用 resolve
        // 如果是 Promise 对象,查看这个 Promise 对象的返回结果,
        // 再根据返回结果来决定调用 resolve 还是 reject
        // resolve(x);
        resolveRromise(promise2, x, resolve, reject);
      } catch (err) {
        // 将异常传递给下一个 then 方法的错误处理回调函数,
        reject(err);
      }
    }, 0);
  });
}

经过上面的处理,再执行回到函数的时候就无需传值了.

...
this.successCallback.shift()();
...
this.failCallback.shift()();

将 then 方法变为可选参数

Promise 的 then 方法的参数是可选的,如果调用 then 方法的时候,不传递参数,这种情况下,Promise 的流程要怎么走呢?

const promise = new MyPromise((resolve, reject) => {
  resolve(100);
});
promies
  .then()
  .then()
  .then(value => console.log(value)) // 100

在 then 方法不传递参数的时候,其情况等同于这样的

promise
  .then(value => value) // 接收到参数,并将这个参数直接返回

这样的话,promise状态就会传递到下一个 then 方法


  then(successCallback, failCallback) {
    // 判断是否有传递参数进来
    successCallback = successCallback ? successCallback : value => value;
    failCallback = failCallback ? failCallback : reason => { throw reason };
    ...
  }

Promise.all 方法

  • 使用 Promise.all 方法可以按照顺序去执行异步任务,并得到顺序的结果

    const p1 = function() {
    return Promise(resolve => {
      setTimeout(() => {
        resolve(\'p1\');
      }, 2000)
    })
    }
    const p2 = function() {
    return new Promise(resolve => {
      resolve(\'p2\');
    })
    }
    
    Promise.all([\'a\', \'b\', p1(), p2(), \'c\']).then(results => {
    // [\'a\', \'b\', \'p1\', \'p2\', \'c\']
    })

    2s 后输出 [\'a\', \'b\', \'p1\', \'p2\', \'c\']

  • Promise.all 方法接收一个数组,内部会按照数组元素的顺序执行,全部执行完之后,再将结果一起返回,返回的结果也是数组,与传入的数组相对应。
  • 如果有一个失败了。返回的结果就是失败的。

  // 静态方法,可以直接使用 MyPromise.all
  // 接收一个数组
  static all(array) {
    // 定义 result,用来存储结果
    const result = [];
    // 记录已经执行完毕的数组元素个数
    let index = 0;
    // 返回一个 promise 对象
    return new MyPromise((resolve, reject) => {
      // 将数据添加到目标数据中
      function addRes(i, value) {
        result[i] = value;
        // 执行完毕+1
        index++;
        // 如果数组中所有元素都执行完毕,才 resolve 结果
        if (index === array.length) resolve(result);
      }  
      // 遍历数组,并判断每个数组元素是普通值还是 Promise 对象,
      // 如果是普通值,直接将元素放到结果集中,
      // 如果是Promise 对象,需要获取其返回值,然后放到结果集中
      for (let i = 0; i < array.length; i++) {
        let current = array[i];
        // 判断是否是 promise 对象
        if (current instanceof MyPromise) {
          current.then(value => addRes(i, value), reason => reject(reason))
        } else {
          addRes(i, current);
        }
      }
    })
  }

Promise.resolve 方法

  • Promise.resolve 方法 可以将给定的一个值转化为一个 Promise 对象。
  • Promise.resolve 对象返回的就是一个 Promise 对象,这个对象包裹着给定的这个值。

    Promise.resolve(10).then(value => console.log(value)); // 10
  • 如果传入的是一个Promise 对象,它会原封不动的将这个 Promise 对象返回
function p1 () {
  return new Promise((resolve, reject) => {
    resolve(100)
  })
}
Promise.resolve(p1()).then(value => console.log(value)); // 100
// resolve 静态方法
static resolve(value) {
    // 如果value 是Promise 对象,直接返回value
    if (value instanceof MyPromise) return value;
    
    // 如果是普通值,返回 Promise 对象,并将value 包裹
    return new MyPromise(resolve => resolve(value));
}

finally

  • Promise 的状态不管成功还是失败的,finally 的回调函数都会被调用
  • 非类方法,接收一个回调函数,无论promise对象最终是成功还是失败,finally方法的回调函数始终会被执行一次
  • 可以链式调用then,可以拿到当前这个promise对象返回的结果
  • finally 方法回调函数中 return 可以返回 Promise对象,后续 then 方法的回调函数应该等待 这个 Promise 执行完才能继续进行

    finally(callback){ 
    return this.then(value=>{  //then返回一个promise对象
        callback() //无论成功失败都会执行
        return value; //传递给下一个then
    },reason=>{
        callback()
        throw reason;
    })
    }

    由于callback也可以返回promise,所以用resolve改造一下

注意返回的value和reason是下面例子中promise的value,不是callback中p1这个promise的返回值

finally(callback){ 
  return this.then(value=>{
      return MyPromise.resolve(callback()).then(()=>value)  //注意value为链式调用的value不是callback方法里的value
  },reason=>{
      return MyPromise.resolve(callback()).then(()=>{throw reason}) //注意reason
  })
}

调用

const promise = new MyPromise((resolve, reject) => {
  reject(\'失败\');
});

const p1 = function () {
  return new MyPromise(resolve => {
    setTimeout(() => {
      resolve(\'2s的异步逻辑\');
    }, 2000);
  })
}

promise.finally(() => {
  console.log(\'finally\');
  return p1(); // return Promise 对象
}).then(value => {
  console.log(value);
}, reason => {
  console.log(reason);
})
finally
失败

catch 方法

  • catch 方法是用来处理当前 Promise 对象最终状态为失败的情况的。
  • 使用它,我们可以不用在 then 方法中传递失败的回调。

只需要在 catch 方法内部去调用 then 方法即可,然后将 then 方法的成功回调传入 undefined,失败的传入一个回调函数

  catch(failCallback) {
    return this.then(undefined, failCallback);
  }

测试

promise.finally(() => {
  console.log(\'finally\');
  return p1(); // return Promise 对象
}).then(value => {
  console.log(value);
}).catch(reason => {
  console.log(\'catch\', reason)
})
finally
catch 失败

以上是关于promise内部实现的主要内容,如果未能解决你的问题,请参考以下文章

promise内部实现

前端面试题之手写promise

剖析Promise内部结构,一步一步实现一个完整的能通过所有Test case的Promise类

澄清 node.js + promises 片段

Promise源码解析

为啥没有通过在 promise 中调用的函数内部的闭包来定义解析?