关于promise:promise内部实现

36次阅读

共计 13265 个字符,预计需要花费 34 分钟才能阅读完成。

从本文你将理解到

  • 什么是 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 失败 

正文完
 0