0

0

深入理解JS中promise

yulia

yulia

发布时间:2018-09-10 14:52:32

|

1622人浏览过

|

来源于php中文网

原创

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

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

TL;DR
1、promise 的工作机制与 callback 类似,都采用内部的抽象操作 Job 来实现异步
2、Promise 构造函数里的 resolve/reject 函数是内部创建的,在调用它们时传入的参数就是要解析的结果,把它和 promise 已经存储的用户传入的处理函数一起插入到 Job 队列中。传入的参数也可以是一个 promise,在 Promise.all/race 的内部就有用到。
3、Promise.prototype.then 根据当前的 promise 的状态来决定是立即将 promise 中存储的结果取出并和参数中的处理函数一起直接插入到 Job 队列中还是先与 promise 关联起来作为结果处理函数。then 会隐式调用 Promise 构建函数构建新的 promise 并返回。
4、Promise.all 先创建一个新的 promise,然后先、初始化一个空的结果数组和一个计数器来对已经 resolve 的 promise进行计数,之后会进行迭代,对于每个迭代值它都会为其创造一个promise,并设定这个promise的then为向结果数组里添加结果以及计数器--,当计数器减至0时就会resolve最终结果。
5、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 function 和 reject 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 function 和 reject 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。之后的情况就相对复杂一些了。

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

ES 规定实现里至少要有两个 Job 队列,ScriptJobs 和 PromiseJobs。当我们调用 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 function 和 reject 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 function 和 reject function,之后就结合迭代器循环:1.如果迭代完了并且计数器为0则调用 promiseCapability 的 resolve 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,感兴趣的话可以继续了解一下。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
es6新特性
es6新特性

es6新特性有:1、块级作用域变量;2、箭头函数;3、模板字符串;4、解构赋值;5、默认参数;6、 扩展运算符;7、 类和继承;8、Promise。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

103

2023.07.17

es6新特性有哪些
es6新特性有哪些

es6的新特性有:1、块级作用域;2、箭头函数;3、解构赋值;4、默认参数;5、扩展运算符;6、模板字符串;7、类和模块;8、迭代器和生成器;9、Promise对象;10、模块化导入和导出等等。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

195

2023.08.04

JavaScript ES6新特性
JavaScript ES6新特性

ES6是JavaScript的根本性升级,引入let/const实现块级作用域、箭头函数解决this绑定问题、解构赋值与模板字符串简化数据处理、对象简写与模块化提升代码可读性与组织性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

221

2025.12.24

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

443

2023.08.02

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

513

2023.06.20

js获取当前时间
js获取当前时间

JS全称JavaScript,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言;它是一种属于网络的高级脚本语言,主要用于Web,常用来为网页添加各式各样的动态功能。js怎么获取当前时间呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

244

2023.07.28

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js是什么意思
js是什么意思

JS是JavaScript的缩写,它是一种广泛应用于网页开发的脚本语言。JavaScript是一种解释性的、基于对象和事件驱动的编程语言,通常用于为网页增加交互性和动态性。它可以在网页上实现复杂的功能和效果,如表单验证、页面元素操作、动画效果、数据交互等。

5306

2023.08.17

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

15

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

相关下载

更多

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号