明白 Promise 的事情道理

原文: https://blog.coding.net/blog/how-do-promises-work

Javascript 采纳回调函数(callback)来处置惩罚异步编程。从同步编程到异步回调编程有一个顺应的历程,然则假如涌现多层回调嵌套,也就是我们常说的恶运的回调金字塔(Pyramid of Doom),相对是一种蹩脚的编程体验。因而便有了 CommonJS 的 Promises/A 范例,用于处理回调金字塔题目。本文先引见 Promises 相干范例,然后再经由过程解读一个迷你的 Promises 以加深明白。

什么是 Promise

一个 Promise 对象代表一个如今还不可用,然则在未来的某个时候点能够被剖析的值。它许可你以一种同步的体式格局编写异步代码。比方,假如你想要运用 Promise API 异步挪用一个长途的效劳器,你须要建立一个代表数据将会在将因由 Web 效劳返回的 Promise 对象。唯一的题目是如今数据还不可用。当要求完成并从效劳器返回时数据将变成可用数据。在此期间, Promise 对象将饰演一个实在数据的代办角色。接下来,你能够在 Promise 对象上绑定一个回调函数,一旦实在数据变得可用这个回调函数将会被挪用。

Promise 对象曾以多种形式存在于很多言语中。

去除恶运的回调金字塔(Pyramid of Doom)

Javascript 中最罕见的反形式做法是回调内部再嵌套回调。

// 回调金字塔
asyncOperation(function(data){
  // 处置惩罚 `data`
  anotherAsync(function(data2){
      // 处置惩罚 `data2`
      yetAnotherAsync(function(){
          // 完成
      });
  });
});

引入 Promises 今后的代码

promiseSomething()
.then(function(data){
    // 处置惩罚 `data`
    return anotherAsync();
})
.then(function(data2){
    // 处置惩罚 `data2`
    return yetAnotherAsync();
})
.then(function(){
    // 完成
});

Promises 将嵌套的 callback ,改形成一系列的.then的联缀挪用,去除了层层缩进的蹩脚代码作风。 Promises 不是一种处理具体题目的算法,罢了一种更好的代码组织形式。接收新的组织形式同时,也逐步以全新的视角来明白异步挪用。

各个言语平台都有响应的 Promise 完成

  • Java’s java.util.concurrent.Future

  • Python’s Twisted deferreds and PEP-3148 futures

  • F#’s Async

  • .Net’s Task

  • C++ 11’s std::future

  • Dart’s Future

  • Javascript’s Promises/A/B/D/A+

下面我来置信相识一下 javascript 言语环境下各个范例的一些细节。

Promises/A 范例

promise 示意一个最终值,该值由一个操纵完成时返回。

  • promise 有三种状况:未完成 (unfulfilled),完成 (fulfilled) 和失利 (failed)。

  • promise 的状况只能由未完成转换成完成,或许未完成转换成失利

  • promise 的状况转换只发作一次。

promise 有一个 then 要领, then 要领能够接收 3 个函数作为参数。前两个函数对应 promise 的两种状况 fulfilled 和 rejected 的回调函数。第三个函数用于处置惩罚进度信息(对进度回调的支撑是可选的)。

promiseSomething().then(function(fulfilled){
        //当 promise 状况变成 fulfilled 时,挪用此函数
    },function(rejected){
        //当 promise 状况变成 rejected 时,挪用此函数
    },function(progress){
        //当返回进度信息时,挪用此函数
    });

假如 promise 支撑以下连个附加要领,称之为可交互的 promise

  • get(propertyName):取得当前 promise 最终值上的一个属性,返回值是一个新的 promise

  • call(functionName, arg1, arg2, ...):挪用固然 promise 最终值上的一个要领,返回值也是一个新的 promise

Promises/B 范例

在 Promises/A 的基础上, Promises/B 定义了一组 promise 模块须要完成的 API

when(value, callback, errback_opt)
假如 value 不是一个 promise ,那末下一事宜轮回 callback 会被挪用, value 作为 callback 的传入值。假如 value 是一个 promise , promise 的状况已完成或许变成完成时,那末下一事宜轮回 callback 会被挪用, resolve 的值会被传入 callback ; promise 的状况已失利或许变成失利时,那末下一事宜轮回 errback 会被挪用, reason 会作为失利的来由传入 errback 。

asap(value, callback, errback_opt)
与 when 最大的区分,假如 value 不是一个 promise ,会被马上实行,不会比及下一事宜轮回。

enqueue(task Function)
尽量快地在接下来的事宜轮回挪用 task 要领。

get(object, name)
返回一个取得对象属性的 promise 。

post(object, name, args)
返回一个挪用对象要领的 promise 。

put(object, name, value)
返回一个修改对象属性的 promise 。

del(object, name)
返回一个删除对象属性的 promise 。

makePromise(descriptor Object, fallback Function)
返回一个 promise 对象,该对象必需是一个可挪用的函数,也多是可被实例化的组织函数。

  • 第一个参数接收一个形貌对象,该对象构造以下

    {
        "when": function(errback){...},
        "get": function(name){...},
        "put": function(name, value){...},
        "post": function(name, args){...},
        "del": function(name){...},
    }

    上面每个注册的 handle 都返回一个 resolved value 或许 promise 。

  • 第二个参数接收一个 fallback(message,…args) 函数,当没有 promise 对象没有找到对应的 handle 时该函数会被触发,返回一个 resolved value 或许 promise 。

defer()
返回一个对象,该对象包括一个 resolve(value) 要领和一个 promise 属性。
当 resolve(value) 要领被第一次挪用时, promise 属性的状况变成 完成,一切之前或今后视察该 promise 的 promise 的状况都被转变成 完成。 value 参数假如不是一个 promise ,会被包装成一个 promise 的 ref 。 resolve 要领会疏忽今后的一切挪用。

reject(reason String)
返回一个被标记为 失利 的 promise 。

一个失利的 promise 上被挪用 when(message) 要领时,会采纳以下两种要领之一

  1. 假如存在 errback , errback 会以 reason 作为参数被挪用。 when 要领会将 errback 的返回值返回。

  2. 假如不存在 errback , when 要领返回一个新的 reject 状况的 promise 对象,以统一 reason 作为参数。

ref(value)
假如 value 是 promise 对象,返回 value 自身。不然,返回一个 resolved 的 promise ,照顾以下 handle 。

  1. when(errback),疏忽 errback ,返回 resolved 值

  2. get(name),返回 resolved 值的对应属性。

  3. put(name, value) ,设置 resolved 值的对应属性。

  4. del(name),删除 resolved 值的对应属性。

  5. post(name, args), 挪用 resolved 值的对应要领。

  6. 其他一切的挪用都返回一个 reject ,并照顾 “Promise does not handle NAME” 的来由。

isPromise(value) Boolean
推断一个对象是不是是 promise

method(name String)
取得一个返回 name 对应要领的 promise 。返回值是 “get”, “put”, “del” 和 “post” 对应的要领,然则会在下一事宜轮回返回。

Promises/D 范例

为了增添差别 promise 完成之间的可互操纵性, Promises/D 范例对 promise 对象和 Promises/B 范例做了进一步的商定。以到达鸭子范例的结果( Duck-type Promise )。

简朴来讲 Promises/D 范例,做了两件事变,

  1. 怎样推断一个对象是 Promise 范例。

  2. 对 Promises/B 范例举行细节补充。

鉴别一个 Promise 对象

Promise 对象必需是完成 promiseSend 要领。

  1. 在 promise 库高低文中,假如对象包括 promiseSend 要领就能够鉴别为 promise 对象

  2. promiseSend 要领必需接收一个操纵称号,作为第一个参数

  3. 操纵称号是一个可扩大的鸠合,下面是一些保存称号

    1. when 此时第三个参数必需是 rejection 回调,rejection 回调必需接收一个 rejection 缘由(能够是任何值)作为第一个参数

    2. get 此时第三个参数为属性名(字符串范例)

    3. put 此时第三个参数为属性名(字符串范例),第四个参数为新属性值。

    4. del 此时第三个参数为属性名

    5. post 此时第三个参数为要领的属性名,接下来的变参为要领的挪用参数

    6. isDef

  4. promiseSend要领的第二个参数为 resolver 要领

  5. promiseSend要领能够接收变参

  6. promiseSend要领必需返回undefined

对 Promises/B 范例的补充

Promises/D 范例中对 Promises/B 范例中定义的 ref 、 reject 、 def 、 defer 要领做了进一步仔细的束缚,此处略去这些细节。

Promises/A+ 范例

前面提到的 Promises/A/B/D 范例都是有 CommonJS 组织提出的, Promises/A+是有一个自称为Promises/A+ 组织宣布的,该范例是以 Promises/A 作为基础举行补充和订正,旨在进步 promise 完成之间的可互操纵性。

Promises/A+ 对.then要领举行仔细的补充,定义了仔细的Promise Resolution Procedure流程,而且将.then要领作为 promise 的对象鉴别要领。

另外, Promises/A+ 还供应了兼容性测试东西,以肯定各个完成的兼容性。

完成一个迷你版本的 Promise

上面扯了这么多范例,如今我们看看怎样完成一个简朴而短小的 Promise 。

状况机

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value or error once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers attached by calling .then or .done
  var handlers = [];
}

状况变迁

仅支撑两种状况变迁, fulfill 和 reject

// ...

function Promise() {
    // ...

  function fulfill(result) {
    state = FULFILLED;
    value = result;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }

}

fulfill 和 reject 要领较为底层,一般更高等的 resolve 要领开放给外部。

// ...

function Promise() {

  // ...

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }
}

resolve 要领能够接收一个一般值或许另一个 promise 作为参数,假如接收一个 promise 作为参数,守候其完成。 promise 不许可被另一个 promise fulfill ,所以须要开放 resolve 要领。 resolve 要领依靠一些协助要领定义以下:

/**
 * Check if a value is a Promise and, if it is,
 * return the `then` method of that promise.
 *
 * @param {Promise|Any} value
 * @return {Function|Null}
 */
function getThen(value) {
  var t = typeof value;
  if (value && (t === 'object' || t === 'function')) {
    var then = value.then;
    if (typeof then === 'function') {
      return then;
    }
  }
  return null;
}

/**
 * Take a potentially misbehaving resolver function and make sure
 * onFulfilled and onRejected are only called once.
 *
 * Makes no guarantees about asynchrony.
 *
 * @param {Function} fn A resolver function that may not be trusted
 * @param {Function} onFulfilled
 * @param {Function} onRejected
 */
function doResolve(fn, onFulfilled, onRejected) {
  var done = false;
  try {
    fn(function (value) {
      if (done) return
      done = true
      onFulfilled(value)
    }, function (reason) {
      if (done) return
      done = true
      onRejected(reason)
    })
  } catch (ex) {
    if (done) return
    done = true
    onRejected(ex)
  }
}

这里 resolve 和 doResolve 之间的递归很奇妙,用来处置惩罚 promise 的层层嵌套( promise 的 value 是一个 promise )。

组织器

// ...

function Promise(fn) {
    // ...
    doResolve(fn, resolve, reject);
}

.done 要领

// ...
function Promise(fn) {
  // ...

  function handle(handler) {
    if (state === PENDING) {
      handlers.push(handler);
    } else {
      if (state === FULFILLED &&
        typeof handler.onFulfilled === 'function') {
        handler.onFulfilled(value);
      }
      if (state === REJECTED &&
        typeof handler.onRejected === 'function') {
        handler.onRejected(value);
      }
    }
  }

  this.done = function (onFulfilled, onRejected) {
    // ensure we are always asynchronous
    setTimeout(function () {
      handle({
        onFulfilled: onFulfilled,
        onRejected: onRejected
      });
    }, 0);
  }
  // ...
}

.then 要领

// ...
function Promise(fn) {
    // ...
    this.then = function (onFulfilled, onRejected) {
      var self = this;
      return new Promise(function (resolve, reject) {
        return self.done(function (result) {
          if (typeof onFulfilled === 'function') {
            try {
              return resolve(onFulfilled(result));
            } catch (ex) {
              return reject(ex);
            }
          } else {
            return resolve(result);
          }
        }, function (error) {
          if (typeof onRejected === 'function') {
            try {
              return resolve(onRejected(error));
            } catch (ex) {
              return reject(ex);
            }
          } else {
            return reject(error);
          }
        });
      });
    }
    // ...
}

$.promise

jQuery 1.8 之前的版本, jQuery 的 then 要领只是一种能够同时挪用 done 、 fail 和 progress 这三种回调的速写要领,而 Promises/A 范例的 then 在行动上更像是 jQuery 的 pipe 。 jQuery 1.8 修改了这个题目,使 then 成为 pipe 的同义词。不过,由于向后兼容的题目, jQuery 的 Promise 再怎样对 Promises/A 示好也不太会招人待见。

另外,在 Promises/A 范例中,由 then 要领天生的 Promise 对象是已实行照样已谢绝,取决于由 then 要领挪用的谁人回调是返回值照样抛出毛病。在 JQuery 的 Promise 对象的回调中抛出毛病是个蹩脚的主张,由于毛病不会被捕捉。

小结

末了一个例子展现了,完成 Promise 的症结是完成好 doResolve 要领,在完事今后触发还调。而为了保证异步 setTimeout(fun, 0); 是症结一步。

Promise 一向用得蛮随手的,其很好的优化了 NodeJS 异步处置惩罚时的代码构造。然则关于其事情道理却有些懵懂和猎奇。因而花了些司理查阅并翻译了 Promise 的范例,以充足的明白 Promise 的细节。

参考浏览

  1. Promises/A

  2. Promises/B

  3. Promises/D

  4. Promisejs

  5. Promises/A+

  6. As soon as possible

  7. A minimalist implementation of a javascript promise

  8. Lightweight implementation of promises

  9. How is a promise/defer library implemented?

  10. Basic Javascript promise implementation attempt

11. You’re Missing the Point of Promises
12. Boom! Promises/A+ Was Born
13. Futures and promises
14. JavaScript Promises – There and back again

《明白 Promise 的事情道理》

Vangie Du

未来的你,一定会谢谢如今冒死勤奋的本身!

    原文作者:CODING
    原文地址: https://segmentfault.com/a/1190000004325757
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞