深入 Promise(一)——Promise 实现详解

if (typeof Promise === ‘undefined‘) {
  return}

实现 Promise/A+ 规范的库有很多,lie 是一个精简的实现 Promise/A+ 的库,并且通过了 Promise/A+ 专门的测试集,但 lie 的代码写的有点绕,我在 lie 的代码基础上进行了修改,使之更容易阅读和理解,并发布了 appoint 模块供大家参考。

Promise/A+ 规范

Promise 规范有很多,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+,有兴趣的可以去了解下,最终 ES6 中采用了 Promise/A+ 规范。在讲解 Promise 实现之前,当然要先了解 Promise/A+ 规范。Promise/A+ 规范参考:

注意:没有特殊说明以下 promise 均指代 Promise 实例。

规范虽然不长,但细节也比较多,我挑出几个要点简单说明下:

  1. Promise 本质是一个状态机。每个 promise 只能是 3 种状态中的一种:pending、fulfilled 或 rejected。状态转变只能是 pending -> fulfilled 或者 pending -> rejected。状态转变不可逆。
  2. then 方法可以被同一个 promise 调用多次。
  3. then 方法必须返回一个 promise。规范里没有明确说明返回一个新的 promise 还是复用老的 promise(即 return this),大多数实现都是返回一个新的 promise,而且复用老的 promise 可能改变内部状态,这与规范也是相违背的。
  4. 值穿透。下面会细讲。

从头实现 Promise

我们知道 Promise 是一个构造函数,需要用 new 调用,并有以下几个 api:

function Promise(resolver) {}Promise.prototype.then = function() {}Promise.prototype.catch = function() {}Promise.resolve = function() {}Promise.reject = function() {}Promise.all = function() {}Promise.race = function() {}

下面我们以 appoint 为最终目标,开始一步一步构建完整的 Promise 实现。

‘use strict‘;var immediate = require(‘immediate‘);function INTERNAL() {}function isFunction(func) {
  return typeof func === ‘function‘;}function isObject(obj) {
  return typeof obj === ‘object‘;}function isArray(arr) {
  return Object.prototype.toString.call(arr) === ‘[object Array]‘;}var PENDING = 0;var FULFILLED = 1;var REJECTED = 2;module.exports = Promise;function Promise(resolver) {
  if (!isFunction(resolver)) {
    throw new TypeError(‘resolver must be a function‘);
  }
  this.state = PENDING;
  this.value = void 0;
  this.queue = [];
  if (resolver !== INTERNAL) {
    safelyResolveThen(this, resolver);
  }}

immediate 是一个将同步转异步执行的库。INTERNAL 就是一个空函数,类似于一些代码库中的 noop。定义了 3 个辅助函数:isFunction、isObject 和 isArray。定义了 3 种状态:PENDING、FULFILLED 和 REJECTED。safelyResolveThen 后面讲。promise 内部有三个变量:

  1. state: 当前 promise 的状态,初始值为 PENDING。状态改变只能是 PENDING -> FULFILLED 或 PENDING -> REJECTED。
  2. value: 当 state 是 FULFILLED 时存储返回值,当 state 是 REJECTED 时存储错误。
  3. queue: promise 内部的回调队列,这是个什么玩意儿?为什么是一个数组?

Promise 实现基本原理

先看一段代码:

var Promise = require(‘appoint‘)var promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve(‘haha‘)
  }, 1000)})var a = promise.then(function onSuccess() {})var b = promise.catch(function onError() {})console.log(require(‘util‘).inspect(promise, { depth: 10 }))console.log(promise.queue[0].promise === a)console.log(promise.queue[1].promise === b)

打印出:

Promise {
  state: 0,
  value: undefined,
  queue:
   [ QueueItem {
       promise: Promise { state: 0, value: undefined, queue: [] },
       callFulfilled: [Function],
       callRejected: [Function] },
     QueueItem {
       promise: Promise { state: 0, value: undefined, queue: [] },
       callFulfilled: [Function],
       callRejected: [Function] } ] }truetrue

可以看出,queue 数组中有两个对象。因为规范中规定:then 方法可以被同一个 promise 调用多次。上例中在调用 .then 和 .catch 时 promise 并没有被 resolve,所以将 .then 和 .catch 生成的新 promise(a 和 b) 和正确时的回调(onSuccess 包装成 callFulfilled)和错误时的回调(onError 包装成 callRejected)生成一个 QueueItem 实例并 push 到 queue 数组里,所以上面两个 console.log 打印 true。当 promise 状态改变时遍历内部 queue 数组,统一执行成功(FULFILLED -> callFulfilled)或失败(REJECTED -> callRejected)的回调(传入 promise 的 value 值),生成的结果分别设置 a 和 b 的 state 和 value,这就是 Promise 实现的基本原理。
再来看另一个例子:

var Promise = require(‘appoint‘)
var promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve(‘haha‘)
  }, 1000)
})
promise
  .then(() => {})
  .then(() => {})
  .then(() => {})
console.log(require(‘util‘).inspect(promise, { depth: 10 }))

打印出:

Promise {
  state: 0,
  value: undefined,
  queue:
   [ QueueItem {
       promise:
        Promise {
          state: 0,
          value: undefined,
          queue:
           [ QueueItem {
               promise:
                Promise {
                  state: 0,
                  value: undefined,
                  queue:
                   [ QueueItem {
                       promise: Promise { state: 0, value: undefined, queue: [] },
                       callFulfilled: [Function],
                       callRejected: [Function] } ] },
               callFulfilled: [Function],
               callRejected: [Function] } ] },
       callFulfilled: [Function],
       callRejected: [Function] } ] }

调用了 3 次 then,每个 then 将它生成的 promise 放到了调用它的 promise 队列里,形成了 3 层调用关系。当最外层的 promise 状态改变时,遍历它的 queue 数组调用对应的回调,设置子 promise 的 state 和 value 并遍历它的 queue 数组调用对应的回调,然后设置孙 promise 的 state 和 value 并遍历它的 queue 数组调用对应的回调......依次类推。

safelyResolveThen

function safelyResolveThen(self, then) {
  var called = false;
  try {
    then(function (value) {
      if (called) {
        return;
      }
      called = true;
      doResolve(self, value);
    }, function (error) {
      if (called) {
        return;
      }
      called = true;
      doReject(self, error);
    });
  } catch (error) {
    if (called) {
      return;
    }
    called = true;
    doReject(self, error);
  }
}

safelyResolveThen 顾名思义用来『安全的执行 then 函数』,这里的 then 函数指『第一个参数是 resolve 函数第二个参数是 reject 函数的函数』,如下两种情况:

  1. 构造函数的参数,即这里的 resolver:

    new Promise(function resolver(resolve, reject) {
      setTimeout(() => {
        resolve(‘haha‘)
      }, 1000)})
  2. promise 的 then:
    promise.then(resolve, reject)

safelyResolveThen 有 3 个作用:

  1. try...catch 捕获抛出的异常,如:

    new Promise(function resolver(resolve, reject) {
      throw new Error(‘Oops‘)
    })
  2. called 控制 resolve 或 reject 只执行一次,多次调用没有任何作用。即:
    var Promise = require(‘appoint‘)var promise = new Promise(function resolver(resolve, reject) {
      setTimeout(() => {
        resolve(‘haha‘)
      }, 1000)
      reject(‘error‘)})promise.then(console.log)promise.catch(console.error)

    打印 error,不会再打印 haha。

  3. 没有错误则执行 doResolve,有错误则执行 doReject。

doResolve 和 doReject

function doResolve(self, value) {
  try {
    var then = getThen(value);
    if (then) {
      safelyResolveThen(self, then);
    } else {
      self.state = FULFILLED;
      self.value = value;
      self.queue.forEach(function (queueItem) {
        queueItem.callFulfilled(value);
      });
    }
    return self;
  } catch (error) {
    return doReject(self, error);
  }}function doReject(self, error) {
  self.state = REJECTED;
  self.value = error;
  self.queue.forEach(function (queueItem) {
    queueItem.callRejected(error);
  });
  return self;}

doReject 就是设置 promise 的 state 为 REJECTED,value 为 error,callRejected 如前面提到的通知子 promise:『我这里出了点问题呀』然后子 promise 根据传入的错误设置自己的状态和值。doResolve 结合 safelyResolveThen 使用不断地解包 promise,直至返回值是非 promise 对象后,设置 promise 的状态和值,然后通知子 promise:『我这里有值了哟』然后子 promise 根据传入的值设置自己的状态和值。

这里有个辅助函数 getThen:

function getThen(obj) {
  var then = obj && obj.then;
  if (obj && (isObject(obj) || isFunction(obj)) && isFunction(then)) {
    return function appyThen() {
      then.apply(obj, arguments);
    };
  }
}

规范中规定:如果 then 是函数,将 x(这里是 obj) 作为函数的 this 调用。

Promise.prototype.then 和 Promise.prototype.catch

Promise.prototype.then = function (onFulfilled, onRejected) {
  if (!isFunction(onFulfilled) && this.state === FULFILLED ||
    !isFunction(onRejected) && this.state === REJECTED) {
    return this;
  }
  var promise = new this.constructor(INTERNAL);
  if (this.state !== PENDING) {
    var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
    unwrap(promise, resolver, this.value);
  } else {
    this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
  }
  return promise;};Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);};

上述代码中的 return this 实现了值穿透,后面会讲。可以看出,then 方法中生成了一个新的 promise 然后返回,符合规范要求。如果 promise 的状态改变了,则调用 unwrap,否则将生成的 promise 加入到当前 promise 的回调队列 queue 里,之前讲解了如何消费 queue。有 3 点需要讲解:

  1. Promise 构造函数传入了一个 INTERNAL 即空函数,因为这个新产生的 promise 可以认为是内部的 promise,需要根据外部的 promise 的状态和值产生自身的状态和值,不需要传入回调函数,而外部 Promise 需要传入回调函数决定它的状态和值。所以之前 Promise 的构造函数里做了判断区分外部调用还是内部调用:

    if (resolver !== INTERNAL) {
      safelyResolveThen(this, resolver);}
  2. unwrap 代码如下:
    function unwrap(promise, func, value) {
      immediate(function () {
        var returnValue;
        try {
          returnValue = func(value);
        } catch (error) {
          return doReject(promise, error);
        }
        if (returnValue === promise) {
          doReject(promise, new TypeError(‘Cannot resolve promise with itself‘));
        } else {
          doResolve(promise, returnValue);
        }
      });}

    从名字也可以理解是用来解包(即执行函数)的,第一个参数是子 promise,第二个参数是父 promise 的 then 的回调(onFulfilled/onRejected),第三个参数是父 promise 的值(正常值/错误)。有 3 点需要说明:

    1. 使用 immediate 将同步代码变异步。如:

      var Promise = require(‘appoint‘)var promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(‘haha‘)
        }, 1000)})promise.then(() => {
        promise.then(() => {
          console.log(‘1‘)
        })
        console.log(‘2‘)})

      打印 2 1,去掉 immediate 则打印 1 2。

    2. try...catch 用来捕获 then/catch 内抛出的异常,并调用 doReject,如:
      promise.then(() => {
        throw new Error(‘haha‘)})promise.catch(() => {
        throw new Error(‘haha‘)})
    3. 返回的值不能是 promise 本身,否则会造成死循环,如 [email protected] 下运行:
      var promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(‘haha‘)
        }, 1000)
      })
      var a = promise.then(() => {
        return a
      })
      
      a.catch(console.log)// [TypeError: Chaining cycle detected for promise #<Promise>]
  3. QueueItem 代码如下:
    function QueueItem(promise, onFulfilled, onRejected) {
      this.promise = promise;
      this.callFulfilled = function (value) {
        doResolve(this.promise, value);
      };
      this.callRejected = function (error) {
        doReject(this.promise, error);
      };
      if (isFunction(onFulfilled)) {
        this.callFulfilled = function (value) {
          unwrap(this.promise, onFulfilled, value);
        };
      }
      if (isFunction(onRejected)) {
        this.callRejected = function (error) {
          unwrap(this.promise, onRejected, error);
        };
      }}

    promise 为 then 生成的新 promise(以下称为『子promise』),onFulfilled 和 onRejected 即是 then 参数中的 onFulfilled 和 onRejected。从上面代码可以看出:比如当 promise 状态变为 FULFILLED 时,之前注册的 then 函数,用 callFulfilled 调用 unwrap 进行解包最终得出子 promise 的状态和值,之前注册的 catch 函数,用 callFulfilled 直接调用 doResolve,设置队列里子 promise 的状态和值。当 promise 状态变为 REJECTED 类似。

注意:promise.catch(onRejected) 就是 promise.then(null, onRejected) 的语法糖。

至此,Promise 的核心实现都完成了。

值穿透

Promise.prototype.then = function (onFulfilled, onRejected) {
  if (!isFunction(onFulfilled) && this.state === FULFILLED ||
    !isFunction(onRejected) && this.state === REJECTED) {
    return this;
  }
  ...};

上面提到了值穿透问题,值穿透即:

var Promise = require(‘appoint‘)
var promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(‘haha‘)
  }, 1000)
})
promise
  .then(‘hehe‘)
  .then(console.log)

最终打印 haha 而不是 hehe。

通过 return this 只实现了值穿透的一种情况,其实值穿透有两种情况:

  1. promise 已经是 FULFILLED/REJECTED 时,通过 return this 实现的值穿透:

    var Promise = require(‘appoint‘)var promise = new Promise(function (resolve) {
      setTimeout(() => {
        resolve(‘haha‘)
      }, 1000)})promise.then(() => {
      promise.then().then((res) => {// ①
        console.log(res)// haha
      })
      promise.catch().then((res) => {// ②
        console.log(res)// haha
      })
      console.log(promise.then() === promise.catch())// true
      console.log(promise.then(1) === promise.catch({ name: ‘nswbmw‘ }))// true})

    上述代码①②处 promise 已经是 FULFILLED 了符合条件所以执行了 return this。注意:原生的 Promise 实现里并不是这样实现的,所以会打印两个 false。

  2. promise 是 PENDING 时,通过生成新的 promise 加入到父 promise 的 queue,父 promise 有值时调用 callFulfilled->doResolve 或 callRejected->doReject(因为 then/catch 传入的参数不是函数)设置子 promise 的状态和值为父 promise 的状态和值。如:
    var Promise = require(‘appoint‘)var promise = new Promise((resolve) => {
      setTimeout(() => {
        resolve(‘haha‘)
      }, 1000)})var a = promise.then()a.then((res) => {
      console.log(res)// haha})var b = promise.catch()b.then((res) => {
      console.log(res)// haha})console.log(a === b)// false

Promise.resolve 和 Promise.reject

Promise.resolve = resolve;function resolve(value) {
  if (value instanceof this) {
    return value;
  }
  return doResolve(new this(INTERNAL), value);}Promise.reject = reject;function reject(reason) {
  var promise = new this(INTERNAL);
  return doReject(promise, reason);}

当 Promise.resolve 参数是一个 promise 时,直接返回该值。

Promise.all

Promise.all = all;function all(iterable) {
  var self = this;
  if (!isArray(iterable)) {
    return this.reject(new TypeError(‘must be an array‘));
  }

  var len = iterable.length;
  var called = false;
  if (!len) {
    return this.resolve([]);
  }

  var values = new Array(len);
  var resolved = 0;
  var i = -1;
  var promise = new this(INTERNAL);

  while (++i < len) {
    allResolver(iterable[i], i);
  }
  return promise;
  function allResolver(value, i) {
    self.resolve(value).then(resolveFromAll, function (error) {
      if (!called) {
        called = true;
        doReject(promise, error);
      }
    });
    function resolveFromAll(outValue) {
      values[i] = outValue;
      if (++resolved === len && !called) {
        called = true;
        doResolve(promise, values);
      }
    }
  }}

Promise.all 用来并行执行多个 promise/值,当所有 promise/值执行完毕后或有一个发生错误时返回。可以看出:

  1. Promise.all 内部生成了一个新的 promise 返回。
  2. called 用来控制即使有多个 promise reject 也只有第一个生效。
  3. values 用来存储结果。
  4. 当最后一个 promise 得出结果后,使用 doResolve(promise, values) 设置 promise 的 state 为 FULFILLED,value 为结果数组 values。

Promise.race

Promise.race = race;function race(iterable) {
  var self = this;
  if (!isArray(iterable)) {
    return this.reject(new TypeError(‘must be an array‘));
  }

  var len = iterable.length;
  var called = false;
  if (!len) {
    return this.resolve([]);
  }

  var i = -1;
  var promise = new this(INTERNAL);

  while (++i < len) {
    resolver(iterable[i]);
  }
  return promise;
  function resolver(value) {
    self.resolve(value).then(function (response) {
      if (!called) {
        called = true;
        doResolve(promise, response);
      }
    }, function (error) {
      if (!called) {
        called = true;
        doReject(promise, error);
      }
    });
  }}

Promise.race 接受一个数组,当数组中有一个 resolve 或 reject 时返回。跟 Promise.all 代码相近,只不过这里用 called 控制只要有任何一个 promise onFulfilled/onRejected 立即去设置 promise 的状态和值。

至此,Promise 的实现全部讲解完毕。

时间: 2024-10-13 12:22:02

深入 Promise(一)——Promise 实现详解的相关文章

Promise对象详解

Promise对象概述(什么是Promise) Promise 是异步编程的一种解决方案,比传统的解决方案--回调函数和事件--更合理和更强大 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果.从语法上说,Promise 是一个对象,从它可以获取异步操作的消息.Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数.此外,Promis

[js高手之路] es6系列教程 - promise常见用法详解(resolve,reject,catch,then,all,race)

关于promise我在之前的文章已经应用过好几次,如[js高手之路]Node.js+jade+express+mongodb+mongoose+promise实现todolist,本文就来讲解下promise的常见用法. 为什么会有promise,他的作用是什么? promise主要是为了解决js中多个异步回调难以维护和控制的问题. 什么是promise? 从图中,我们可以看出,Promise是一个函数,这个函数上有在项目中常用的静态方法:all, race, reject,resolve等,原

关于Promise详解

异步回调 回调地狱 在需要多个操作的时候,会导致多个回调函数嵌套,导致代码不够直观,就是常说的回调地狱 并行结果 如果几个异步操作之间并没有前后顺序之分,但需要等多个异步操作都完成后才能执行后续的任务,无法实现并行节约时间 Promise Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果. 什么时候会用到过一段时间?答案是异步操作,异步是指可能比较长时间才有结果的才做,例如网络请求.读取本地文件等 Promise的三种状态 Pending Promise对象实例创建时

ES6之Promise用法详解

一 前言 本文主要对ES6的Promise进行一些入门级的介绍.要想学习一个知识点,肯定是从三个方面出发,what.why.how.下面就跟着我一步步学习吧~ 二 什么是Promise 首先是what.那么什么是Promise呢? 以下是MDN对Promise的定义 The Promise object is used for asynchronous computations. A Promise represents a single asynchronous operation that

触碰jQuery:AJAX异步详解

触碰jQuery:AJAX异步详解 传送门:异步编程系列目录…… 示例源码:触碰jQuery:AJAX异步详解.rar AJAX 全称 Asynchronous JavaScript and XML(异步的 JavaScript 和 XML).它并非一种新的技术,而是以下几种原有技术的结合体. 1)   使用CSS和XHTML来表示. 2)   使用DOM模型来交互和动态显示. 3)   使用XMLHttpRequest来和服务器进行异步通信. 4)   使用javascript来绑定和调用.

测试框架mochajs详解

测试框架mochajs详解 章节目录 关于单元测试的想法 mocha单元测试框架简介 安装mocha 一个简单的例子 mocha支持的断言模块 同步代码测试 异步代码测试 promise代码测试 不建议使用箭头函数 钩子函数 钩子函数的描述参数 异步的钩子函数 全局钩子 延迟启动测试 测试用例TODO 仅执行一个用例集/用例 跳过哪些用例集/用例 重新执行用例 动态生成用例 测试时间 测试超时 用例集执行超时 用例执行超时 钩子函数超时 diff差异比较功能 mocha使用命令和参数 mocha

Angular.js中处理页面闪烁的方法详解

Angular.js中处理页面闪烁的方法详解 前言 大家在使用{{}}绑定数据的时候,页面加载会出现满屏尽是{{xxx}}的情况.数据还没响应,但页面已经渲染了.这是因为浏览器和angularjs渲染页面都需要消耗一定的时间,这个间隔可能很小,甚至让人感觉不到,这种情况一切正常,但这个时间也可能很长,这时候用户可能会看到满屏尽是{{xxxx}}.这种情况被叫做"Flash Of Unrendered Content (FOUC)(K)?and is always unwanted.".

jQuery的deferred对象详解(转)

jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本. 每个版本都会引入一些新功能.今天我想介绍的,就是从jQuery 1.5.0版本开始引入的一个新功能----deferred对象. 这个功能很重要,未来将成为jQuery的核心方法,它彻底改变了如何在jQuery中使用ajax.为了实现它,jQuery的全部ajax代码都被改写了.但是,它比较抽象,初学者很难掌握,网上的教程也不多.所以,我把自己的学习笔记整理出来了,希望对大家有用. 本文不是初级教程,针对的读者是那些已经具备

触碰jQuery:AJAX异步详解(转)

AJAX 全称 Asynchronous JavaScript and XML(异步的 JavaScript 和 XML).它并非一种新的技术,而是以下几种原有技术的结合体. 1)   使用CSS和XHTML来表示. 2)   使用DOM模型来交互和动态显示. 3)   使用XMLHttpRequest来和服务器进行异步通信. 4)   使用javascript来绑定和调用. 通过AJAX异步技术,可以在客户端脚本与web服务器交互数据的过程中使用XMLHttpRequest对象来完成HTTP请