资讯专栏INFORMATION COLUMN

深入 Promise

cfanr / 2758人阅读

摘要:首先从这个构造函数说起,它是全局对象的属性的值,这也就是为什么浏览器环境下我们能直接调用它的原因,就像这些构造函数一样。的产生就是像正常使用构造函数那样构建一个,不过传给构造函数是内部自动创建的,作用是把记录到中。

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

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

参考: 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 这些构造函数一样。

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

下一步,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。

那么 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, <>),待会再详细说明。

再来看 resolve function,与 reject function 一样,在生成它时,会把它与某个 promise 关联起来。在执行时,我们传入的参数叫做 resolution。如果 promise 已经 resolve 过,就返回 undefined。之后的情况就相对复杂一些了。

如果用户把这个 promise 本身传给了 resolve function 作为参数 resolution,就会创建一个 TypeError,throw 它,并调用 RejectPromise,reason 参数为这个 TypeError。

如果 resolution 的类型不是 Object,就调用 FulfillPromise(promise, resolution)

其余的情况就是 resolution 是除了自身以外的带 then 的对象 (Promise) 的情况了。

如果 resolution 是个不带then的对象,就 RejectPromise

如果有 then 属性但不能调用,也 FulfillPromise, 。

如果有 then 属性并且可以调用,就 EnqueueJob("PromiseJobs", PromiseResolveThenableJob, <>)

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

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

PromiseReactionJob(reaction, argument)
reaction 有三个内部插槽 [[Capability]][[Type]][[Handler]],分别表示 [[关联的 promise 及相关的resolve function 和 reject function]][[类别]][[handler]]。如果用户没有给 handler(undefined),就根据类别是 Fulfill 还是 Reject 来把 argument 当作结果。如果给了 handler,就用它来对 argument 进行进一步处理。最后根据这个结果来用 resolve function 和 reject function 进行处理并返回。

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, <>),最后返回 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,之后就结合迭代器循环:

如果迭代完了并且计数器为0则调用 promiseCapabilityresolve function 来 resolve 结果数组

否则计数器加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)。

欢迎点赞、收藏、留言评论、转发分享和打赏支持我们。打赏将被完全转交给文章作者。

感谢您的阅读。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97773.html

相关文章

  • ES6-7

    摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...

    mudiyouyou 评论0 收藏0
  • [译] 深入理解 Promise 五部曲:4. 扩展问题

    摘要:有一个和相关的更大的问题。最后,请负有责任感并且使用安全的扩展。深入理解五部曲异步问题深入理解五部曲转换问题深入理解五部曲可靠性问题深入理解五部曲扩展性问题深入理解五部曲乐高问题最后,安利下我的个人博客,欢迎访问 原文地址:http://blog.getify.com/promis... 现在,我希望你已经看过深入理解Promise的前三篇文章了。并且假设你已经完全理解Promises...

    Shimmer 评论0 收藏0
  • 深入理解ES6笔记(十一)Promise与异步编程

    摘要:回调函数模式类似于事件模型,因为异步代码也会在后面的一个时间点才执行如果回调过多,会陷入回调地狱基础可以当做是一个占位符,表示异步操作的执行结果。函数可以返回一个,而不必订阅一个事件或者向函数传递一个回调函数。 主要知识点:Promise生命周期、Promise基本操作、Promise链、响应多个Promise以及集成PromiseshowImg(https://segmentfaul...

    RayKr 评论0 收藏0
  • JavaScript基础——深入学习async/await

    摘要:等待的基本语法该关键字的的意思就是让编译器等待并返回结果。这里并不会占用资源,因为引擎可以同时执行其他任务其他脚本或处理事件。接下来,我们写一个火箭发射场景的小例子不是真的发射火箭 本文由云+社区发表 本篇文章,小编将和大家一起学习异步编程的未来——async/await,它会打破你对上篇文章Promise的认知,竟然异步代码还能这么写! 但是别太得意,你需要深入理解Promise后,...

    张金宝 评论0 收藏0
  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0
  • 深入理解promise对象

    摘要:前言中的异步,刚开始的时候都是用回调函数实现的,所以如果异步嵌套的话,就有出现回调地狱,使得代码难以阅读和难以维护,后来出现了,解决了回调地狱的问题。 前言 js中的异步,刚开始的时候都是用回调函数实现的,所以如果异步嵌套的话,就有出现回调地狱,使得代码难以阅读和难以维护,后来es6出现了promise,解决了回调地狱的问题。现在我们就自己写代码实现一下promise,这样才能深入理解...

    CoderDock 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<