美文网首页程序员
探究promise的真实面貌

探究promise的真实面貌

作者: snow_in | 来源:发表于2018-07-27 17:27 被阅读0次

这篇文章的目的就是想探索一下promise是怎么实现的,因为promise一直在用,但是对于它内部具体是怎么实现的却一直不知,所以就想解一解心中的疑惑,掀开promise的神秘面纱 ^_^
首先,我们先通过Promises/A+规范 认识一下promise。

promise是什么

A promise represents the eventual result of an asynchronous operation. —— Promises/A+规范

就是说promise代表一个异步操作的最终结果。与promise进行交互的主要方式是通过then方法,该方法注册回调以接收promise的最终值或无法执行promise的原因。

  1. promise的状态
    promise必须处于以下三种状态之一:pending、fulfilled、rejected。一旦promise变为fulfilled或rejected,就不能再改变为其他的状态。

  2. then 方法
    一个promise必须提供一个then方法来访问其当前或最终的值。then方法接受两个参数

       promise.then(onFulfilled, onRejected)
    
  • onFulfilled, onRejected是可选的参数,如果onFulfilled或 onRejected不是函数,则忽略这个参数
  • 如果onFulfilled是一个函数,它必须在promise执行成功后调用,并且以promise的值作为它的第一个参数,不可以调用多次;如果onRejected是一个函数,它必须在promise执行失败后调用。
  • 在执行上下文仅包含平台代码的时候onFulfilled和onRejected才能被调用。什么是“平台代码”,Promises/A+规范也给出了解释:在实践中要确保onFulfilled和onRejected异步执行,在event loop之后执行then方法。这个可以使用 setTimeout or setImmediate
    macro-task机制,或者MutationObserver or process.nextTickmicro-task机制来实现, 实际上then是属于micro-task
  • then可以在一个promise中调用多次,当 promise 变为或已处于 fulfilled 时,所有 onFulfilled 回调必须按照其通过 then 注册的顺序被执行;当 promise 变为或已处于 rejected 时,所有 onRejected 回调必须按照其通过 then 注册的顺序被执行。
  • then必须返回一个新的promise

接下来,我们从promise的构造函数入手。

promise构造函数

Promises/A+ 规范并没有明确给出Promise构造函数的规则,所以我们根据es6 Promise的用法来构造一个构造函数

function Promise (executor) {
  this._status = 'PENDING'; // promise 的三种状态:PENDING、FULFILLED、REJECTED
  this._value = null;       // 存储promise的执行结果
  this.onResolvedCB = [];   // then() 注册resolve 时的回调函数数组
  this.onRejectedCB = [];   // then() 注册reject 时的回调函数数组

  // resolve和reject是构造函数上的方法,要在内部实现
  function resolve(value) {
    // ...
  }

  function rejected(reason) {
    // ...
  }

  try { // executor执行过程中可能会产生错误,所以用try catch 来捕获
    executor(resolve, reject); // 根据es6我们知道,Promise一旦新建就会立即执行,所以最后执行了回调函数
  } catch (e) {
    reject(e);
  }
}

构造函数的主体算是出来了,接下来要实现resolve和reject两个函数

根据es6的描述,resolve函数的作用是:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

  function resolve(value) {
    this._status = 'FULFILLED'; // 将状态改为已成功('FULFILLED')
    this._value = value;        // 更新当前value值
    for (const fn of this.onResolvedCB) {
      fn(value);                // 执行所有的回调函数
    }
    
    this.onResolvedCB = [];
  }

  function rejected(reason) {
    this._status = 'REJECTED';   // 将状态改为失败('REJECTED')
    this._value = reason;        // 更新当前value值
    for (const fn of this.onRejectedCB) {
      fn(reason);                // 执行所有的回调函数
    }
    
    this.onRejectedCB = [];
  }

then方法

接下来看一下then方法的实现

Promise.prototype.then = function(onResolved, onRejected) {
  const that = this;
  let promise2 = null
  // 根据标准,如果参数不是函数就要忽略它。但是我们要处理值的穿透问题,例如:
  // new Promise((resolve, reject) => {
  //   resolve(1);
  // }).then().then((val) => {console.log(val)}) 也是要打印出 1 的
  // 我们知道then方法回调函数的返回值会作为参数传递给下一个then 方法的回调函数,所以当参数不是函数时,默认是一个又返回值的函数
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) { return value; };
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) { return reason; };

  // 当状态处于已完成时,调用onResolved, 并且返回一个新的Promise
  if (that._status === 'FULFILLED') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() { // 我们知道,then是一个‘micro-task’,回调函数必须在事件循环之后,在一个新的调用栈里异步执行。‘micro-task’ process.nextTick是
        try {                 // node中的函数,所以我们先用setTimeout实现
          const x = onResolved(that._value);
          resolve(x);
        } catch (e) {
          reject(e)
        }
      });
    });
  }

  if (that._status === 'REJECTED') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          const x = onRejected(that._value);
          resolve(x);
        } catch (e) {
          reject(e);
        }
      });
    });
  }

  // 当处于等待状态时,把回调函数分别放入对应当队列,当状态改变时调用
  if (that._status === 'PENDING') {
    return promise2 = new Promise(function(resolve, reject) {
      that.onResolvedCB.push(function(value) {
        setTimeout(function() {
          try {
            const x = onResolved(value);
            resolve(x);
          } catch (e) {
            reject(e)
          }
        });
      });

      that.onRejectedCB.push(function(reason) {
        setTimeout(function() {
          try {
            const x = onRejected(reason);
            resolve(x);
          } catch (e) {
            reject(e)
          }
        })
      });
    });
  }

}

为了使不同的promise(不同的第三方可能会有不同的实现)互相交互,我们遵照规范(The Promise Resolution Procedure)实现一下resolvePromise

function resolvePromise(promise, x, resolve, reject) {
  let then = null;

  if (promise === x) {
    return reject(new TypeError());
  }

  if (x instanceof Promise) {
    if (x._status === 'PENDING') {
      x.then(function(value) {
        resolvePromise(promise, value, resolve, reject)
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }

  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      then = x.then;
    } catch (e) {
      return reject(e);
    }

    if (typeof then === 'function') {
      try {
        then.call(x, function(value) {resolve(value)}, function(reason){reject(reason)})
      } catch (e) {
        reject(e);
      }
    }
  } else {
    resolve(x)
  }
}

然后用这个方法替换then方法中各个状态回调函数返回值的处理,即:

Promise.prototype.then = function(onResolved, onRejected) {
  const that = this;
  let promise2 = null
  // 根据标准,如果参数不是函数就要忽略它。但是我们要处理值的穿透问题,例如:
  // new Promise((resolve, reject) => {
  //   resolve(1);
  // }).then().then((val) => {console.log(val)}) 也是要打印出 1 的
  // 我们知道then方法回调函数的返回值会作为参数传递给下一个then 方法的回调函数,所以当参数不是函数时,默认是一个又返回值的函数
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) { return value; };
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) { return reason; };

  // 当状态处于已完成时,调用onResolved, 并且返回一个新的Promise
  if (that._status === 'FULFILLED') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() { // 我们知道,then是一个‘micro-task’,回调函数必须在事件循环之后,在一个新的调用栈里异步执行。‘micro-task’ process.nextTick是
        try {                 // node中的函数,所以我们先用setTimeout实现
          const x = onResolved(that._value);
+          resolvePromise(promise2, value, resolve, reject);
        } catch (e) {
          reject(e)
        }
      });
    });
  }

  if (that._status === 'REJECTED') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          const x = onRejected(that._value);
+          resolvePromise(promise2, value, resolve, reject);
        } catch (e) {
          reject(e);
        }
      });
    });
  }

  // 当处于等待状态时,把回调函数分别放入对应当队列,当状态改变时调用
  if (that._status === 'PENDING') {
    return promise2 = new Promise(function(resolve, reject) {
      that.onResolvedCB.push(function(value) {
        setTimeout(function() {
          try {
            const x = onResolved(value);
+            resolvePromise(promise2, value, resolve, reject);
          } catch (e) {
            reject(e)
          }
        });
      });

      that.onRejectedCB.push(function(reason) {
        setTimeout(function() {
          try {
            const x = onRejected(reason);
+            resolvePromise(promise2, value, resolve, reject);
          } catch (e) {
            reject(e)
          }
        })
      });
    });
  }

}

以前对Promise的状态改变之后就不可再变的理解有误会,以为Promise抛出错误被catch方法捕获之后后面的then方法就不会执行了,实际上是又返回了一个新的Promise,后面then方法的执行取决于新的Promise的状态。

另外,catch方法也只是then方法的一个语法糖,就相当于
Promise.prototype.then(undefined, onRejected);

可能对Promise的理解有不到位的地方,欢迎小伙伴前来指正~ _

相关文章

  • 探究promise的真实面貌

    这篇文章的目的就是想探索一下promise是怎么实现的,因为promise一直在用,但是对于它内部具体是怎么实现的...

  • 探究promise

    前言 大家都知道Promise是ES6的一个针对回调地狱的一种解决方案,我们可以用promise去封装异步请求,用...

  • Promise技术探究

    1.promise简介 1.1 Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果...

  • 飞虎队的真实历史面貌

    飞虎队,是我国抗战时期一个响亮的名字。作为一支小规模的空军部队,飞虎队虽然存在时间较短,却在1941-1942年间...

  • Promise实现原理(附源码)

    本篇文章主要在于探究 Promise 的实现原理,带领大家一步一步实现一个 Promise , 不对其用法做说明,...

  • 佛教说的宇宙人生真相是什么?

    人为什么会有烦恼?是因为看不清宇宙人生的真实面貌。宇宙人生的真实面貌是清净的、平等的、慈悲的。如何会有妄想、分别、...

  • 女生单身久了的真实面貌

    天天喊着想脱单,看到抖音里的帅哥会犯花痴,萌生恋爱冲动,也想找一个帅气的小哥哥谈一段偶像剧甜甜的恋爱。有浪漫的惊喜...

  • “工作”、“时间”、“底层”的真实面貌

    1.务实一点,你去工作,不是为了寻找一个组织,然后把这个组织称之为“家”,并无私付出。 你去工作,是为了挣钱,获得...

  • 情绪脑----孩子发脾气怎么办

    要从实践的角度出发了解一种现象,就需要我们寻找源头,也就是事物的真实面貌。了解清楚真实面貌再寻求解决方式。 首先还...

  • Promise -- Async container & dec

    基本概念 Promise本质是用于承载异步操作的容器,使得异步操作能够以类似同步的面貌出现,所以then里面内容执...

网友评论

    本文标题:探究promise的真实面貌

    本文链接:https://www.haomeiwen.com/subject/ttahmftx.html