深刻 Promise

> new Promise((resolve, reject) => setTimeout(resolve, 1000, 'foo'))  
> .then(console.log)  
> // foo (1s后)

在使用 Promise 的时候,咱们最简单的理解与用法就是像上面的代码那样,把异步结果提供给 resolve 做参数,而后经过给 then 方法传递一个自定义函数做为结果处理函数。但 resolve 和 reject 这两个参数究竟是什么?在这背后,它的基本工做方式究竟是怎样的呢?让咱们从规范的角度来初步了解它吧。 javascript

参考: ES8 Promise前端

TL;DR

  • promise 的工做机制与 callback 相似,都采用内部的抽象操做 Job 来实现异步
  • Promise 构造函数里的 resolve/reject 函数是内部建立的,在调用它们时传入的参数就是要解析的结果,把它和 promise 已经存储的用户传入的处理函数一块儿插入到 Job 队列中。传入的参数也能够是一个 promise,在 Promise.all/race 的内部就有用到。
  • Promise.prototype.then 根据当前的 promise 的状态来决定是当即将 promise 中存储的结果取出并和参数中的处理函数一块儿直接插入到 Job 队列中仍是先与 promise 关联起来做为结果处理函数。then 会隐式调用 Promise 构建函数构建新的 promise 并返回。
  • Promise.all 先建立一个新的 promise,而后先、初始化一个空的结果数组和一个计数器来对已经 resolve 的 promise进行计数,以后会进行迭代,对于每一个迭代值它都会为其创造一个promise,并设定这个promise的then为向结果数组里添加结果以及计数器--,当计数器减至0时就会resolve最终结果。
  • Promise.race 也是会建立一个新的主 promise,以后主要是根据 promise 只能 resolve 一次的限制,对于每一个迭代值都会创造另外一个promise,先resolve的也就会先被主 promise resolve 返回结果。

new Promise(executor)

首先从 Promise 这个构造函数提及,它是全局对象的 Promise 属性的值,这也就是为何浏览器环境下咱们能直接调用它的缘由,就像 String, Array 这些构造函数同样。 java

new Promise(executor)的第一步就像其余构造函数同样,按照 Promise 的 prototype 来构建一个新对象,并初始化了几个内部插槽[[PromiseState]][[PromiseResult]][[PromiseFullfillReactions]][[PromiseRejectReactions]][[PromiseIsHandled]]来记录一些相关的信息,能够从名字来大体推断出他们的做用,详情咱们下文再提。这里它们的初始值除了[[PromiseResult]]依次为 "pending",空 list,空 list,false。 react

下一步,ES 会根据这个 promise 对象来生成用来resolve promise的 resolve function 和用来 reject promise 的 reject function。而后调用 executor,以 resolve functionreject function 为参数,若是在这个过程当中出错了,就直接 reject promise。最后返回 promise。 数组

那什么又是 resolve,什么又是 reject 呢。咱们知道 Promise 的状态,也就是[[PromiseState]]有三种值: pending, fullfilled, rejected,用 reject function 就能够 reject promise,把它的状态从 pending 变为rejected。不过 resolve function 既能够 fullfill promise 来把promise的状态从 pending 变为 fullfilled,也能够用来 reject promise。 promise

那么 resolve functionreject function 到底作了些什么呢? 浏览器

先来看 reject function ,首先在生成它的时候,会给它初始化[[Promise]][[AlreadyResolved]]插槽,也就是把它和某个 promise 关联起来。在执行时,会传入一个参数 reason,并只有当[[AlreadyResolved]]是 false,也就是还没 resolve 过、状态为 pending 时,才会调用返回 RejectPromise、传入 promise 和 reason 参数来 reject promise,不然返回 undefined。
RejectPromise(promise, reason),除了把[[PromiseState]]从 pending 变为 rejected 以外,还会把 promise 的结果[[PromiseResult]]的值设为 reason,并会取出 promise 的[[PromiseRejectReactions]]中已存的记录(相信读者们已经明白后面还会有一个操做来向这个内部插槽里存记录),并用 TriggerPromiseReactions 调用这些记录作后续处理,并传入 reject 的缘由 reason。相似的,resolve function 中用到的 FullfillPromise(promise, value) 操做把 promise 的状态变为 fulfilled,抽取[[PromiseFullfillReactions]]的值调用 TriggerPromiseReactions,并传入 fulfilled 的结果 value。 微信

TriggerPromiseReactions(reactions, argument) 会调用 EnqueueJob("PromiseJobs", PromiseReactionJob, <<reactions, argument>>),待会再详细说明。 异步

再来看 resolve function,与 reject function 同样,在生成它时,会把它与某个 promise 关联起来。在执行时,咱们传入的参数叫作 resolution。若是 promise 已经 resolve 过,就返回 undefined。以后的状况就相对复杂一些了。async

  1. 若是用户把这个 promise 自己传给了 resolve function 做为参数 resolution,就会建立一个 TypeError,throw 它,并调用 RejectPromise,reason 参数为这个 TypeError。
  2. 若是 resolution 的类型不是 Object,就调用 FulfillPromise(promise, resolution)
  3. 其他的状况就是 resolution 是除了自身之外的带 then 的对象 (Promise) 的状况了。

    • 若是 resolution 是个不带then的对象,就 RejectPromise
    • 若是有 then 属性但不能调用,也 FulfillPromise, 。
    • 若是有 then 属性而且能够调用,就 EnqueueJob("PromiseJobs", PromiseResolveThenableJob, <<promise, resolution, thenAction>>)

在说明 EnqueueJob 以前,先来看看 Job 是个什么东西。简单来讲,它就像是回调的内部实现机制:“当没有其余 ES 在跑时,初始化并执行本身对应的 ES。“。咱们有一个待执行的 FIFO 的 Job 队列,以及当前的执行环境 running execution context 和 execution context stack,当后二者均为空时,才会执行 Job 队列的第一个。

ES 规定实现里至少要有两个 Job 队列,ScriptJobsPromiseJobs。当咱们调用 EnqueueJob("PromiseJobs", ...)时,也就将要完成的 Job 和它们的参数插入到了 PromiseJobs 这个队列。能够看到,Promise 下有两种 Job

  1. PromiseReactionJob(reaction, argument)
    reaction 有三个内部插槽 [[Capability]][[Type]][[Handler]],分别表示 [[关联的 promise 及相关的resolve function 和 reject function]][[类别]][[handler]]。若是用户没有给 handler(undefined),就根据类别是 Fulfill 仍是 Reject 来把 argument 看成结果。若是给了 handler,就用它来对 argument 进行进一步处理。最后根据这个结果来用 resolve function 和 reject function 进行处理并返回。
  2. PromiseResolveThenableJob(promiseToResolve, thenable, then)
    建立和 promiseToResolve 关联的 resolve function 和 reject function。以 then 为调用函数,thenable 为this,resolve function和reject function 为参数调用返回。

Promise.prototype.then(onfulfilled, onrejected)

首先是建立一个 promiseCapability,它包含了一个新的 promise 和相关联的 resolve functionreject function。promise 的产生就是像正常使用 Promise 构造函数那样构建一个 promise,不过传给构造函数 executor 是内部自动建立的,做用是把 resolve/reject function 记录到PromiseCapability中。
根据 promiseCapability 和 onfulfilled/onrejected 建立两个分别用于 fulfill 和 reject 的PromiseReaction,也就是 PromiseJobs 里最终要执行的操做。
若是当前的 promise(this)是 pending 状态,就把这两个 reaction 分别插入到 promise的[[PromiseFulfillReactions]][[PromiseRejectReactions]]队列中。但若是此时 promise 已是 fulfilled 或是 rejected 状态了,就从 promise 的[[PromiseResult]]取出值 result,做为 fulfilled 的结果/reject 的缘由,插入到 Job 队列里,EnqueueJob("PromiseJobs", PromiseReactionJob, <<reaciton, result>>),最后返回 prjomiseCapability 里存储的新 promise。Promise.prototype.catch(onrejected) 就是 Promise.prototype.then(undefined, onrejected)

Promise.resolve(x)

像 then 那样建立一个 promiseCapability,而后直接调用其中的 resolve function 并传入要解析的值x,最后返回其中的新 promise.

Promise.all(iterable)

Promise.all也会像 then 那样建立一个 promiseCapability,里面包含着一个新的 promise 及其关联的 resolve functionreject function,以后就结合迭代器循环:

  1. 若是迭代完了而且计数器为0则调用 promiseCapabilityresolve function 来 resolve 结果数组
  2. 不然计数器加1,而后取出下一个迭代的值,传给 Promise.resolve 也构建一个新的 promise,而后内部建立一个 Promise.all Resolve Element Function,传给这个新 promise 的 then 用来把结果添加到结果数组并使计数器减一。

Promise.race(iterable)

一样的,建立一个 promiseCapability,而后进行迭代,用 Promise.resolve 来构建一个新的 promise,以后调用这个新 promise 的 then 方法,传入 promiseCapability 里的 resolve/reject function,结合以前提到的 promise 只会 resolve 一次,能够看到确实颇有 race 的意味。

结语

看到这里,不知道你们是否对 Promise 有了更深的理解了呢。再往深一步,ES6里新提出的 async/await 实际上也是应用了 Generator 的思想与 Promise,感兴趣的话能够继续了解一下。


文 / Kacxxia

并无做者介绍

本文已由做者受权发布,版权属于创宇前端。欢迎注明出处转载本文。本文连接:https://knownsec-fed.com/2018-08-22-shen-ru-promise/

想要看到更多来自知道创宇开发一线的分享,请搜索关注咱们的微信公众号:创宇前端(KnownsecFED)。

欢迎点赞、收藏、留言评论、转发分享和打赏支持咱们。打赏将被彻底转交给文章做者。

感谢您的阅读。

相关文章
相关标签/搜索