ES6 --- Promise深入学习(二)Promise,then,catch,finally,及使用示例
Promise
想象一下,你是一位顶尖歌手,粉丝没日没夜地询问你下首歌什么时候发。
为了从中解放,你承诺(promise)会在单曲发布的第一时间发给他们。你给了粉丝们一个列表。他们可以在上面填写他们的电子邮件地址,以便当歌曲发布后,让所有订阅了的人能够立即收到。即便遇到不测,例如录音室发生了火灾,以致你无法发布新歌,他们也能及时收到相关通知。
每个人都很开心:你不会被任何人催促,粉丝们也不用担心错过歌曲发行。
这是我们在编程中经常遇到的事儿与真实生活的类比:
- “生产者代码(producing code)”会做一些事儿,并且会需要一些时间。例如,通过网络加载数据的代码。它就像一位“歌手”。
- “消费者代码(consuming code)”想要在“生产者代码”完成工作的第一时间就能获得其工作成果。许多函数可能都需要这个结果。这些就是“粉丝”。
- Promise 是将“生产者代码”和“消费者代码”连接在一起的一个特殊的 JavaScript 对象。用我们的类比来说:这就是就像是“订阅列表”。“生产者代码”花费它所需的任意长度时间来产出所承诺的结果,而 “promise” 将在它(译注:指的是“生产者代码”,也就是下文所说的 executor)准备好时,将结果向所有订阅了的代码开放。
这种类比并不十分准确,因为 JavaScipt 的 promise 比简单的订阅列表更加复杂:它们还拥有其他的功能和局限性。但以此开始挺好的。
Promise 对象的构造器(constructor)语法如下:
let promise = new Promise(function(resolve, reject) {// executor(生产者代码,“歌手”)
});
传递给 new Promise
的函数被称为 executor(执行者)。当 new Promise
被创建,executor 会自动运行。它包含最终应产出结果的生产者代码。按照上面的类比:executor 就是“歌手”。
它的参数 resolve
和 reject
是由 JavaScript 自身提供的回调。我们的代码仅在 executor 的内部。
当 executor 获得了结果,无论是早还是晚都没关系,它应该调用以下回调之一:
resolve(value)
—— 如果任务成功完成并带有结果value
。reject(error)
—— 如果出现了 error,error
即为 error 对象。
所以总结一下就是:executor 会自动运行并尝试执行一项工作。尝试结束后,如果成功则调用 resolve
,如果出现 error 则调用 reject
。
由 new Promise
构造器返回的 promise
对象具有以下内部属性:
state
—— 最初是"pending"
,然后在resolve
被调用时变为"fulfilled"
,或者在reject
被调用时变为"rejected"
。result
—— 最初是undefined
,然后在resolve(value)
被调用时变为value
,或者在reject(error)
被调用时变为error
。
所以,executor 最终将 promise
移至以下状态之一:
稍后我们将看到“粉丝”如何订阅这些更改。
下面是一个 promise 构造器和一个简单的 executor 函数,该 executor 函数具有包含时间(即 setTimeout
)的“生产者代码”:
let promise = new Promise(function(resolve, reject) {// 当 promise 被构造完成时,自动执行此函数// 1 秒后发出工作已经被完成的信号,并带有结果 "done"setTimeout(() => resolve("done"), 1000);
});
通过运行上面的代码,我们可以看到两件事儿:
executor 被自动且立即调用(通过
new Promise
)。executor 接受两个参数:
resolve
和reject
。这些函数由 JavaScipt 引擎预先定义,因此我们不需要创建它们。我们只需要在准备好(译注:指的是 executor 准备好)时调用其中之一即可。经过 1 秒的“处理”后,executor 调用
resolve("done")
来产生结果。这将改变promise
对象的状态:
这是一个成功完成任务的例子,一个“成功实现了的诺言”。
现在的则是一个 executor 以 error 拒绝 promise 的示例:
let promise = new Promise(function(resolve, reject) {// 1 秒后发出工作已经被完成的信号,并带有 errorsetTimeout(() => reject(new Error("Whoops!")), 1000);
});
对 reject(...)
的调用将 promise 对象的状态移至 "rejected"
:
总而言之,executor 应该执行一项工作(通常是需要花费一些时间的事儿),然后调用 resolve
或 reject
来改变对应的 promise 对象的状态。
与最初的 “pending” promise 相反,一个 resolved 或 rejected 的 promise 都会被称为 “settled”。
这儿只能有一个结果或一个 error
executor 只能调用一个
resolve
或一个reject
。任何状态的更改都是最终的。所有其他的再对
resolve
和reject
的调用都会被忽略:let promise = new Promise(function(resolve, reject) {resolve("done");reject(new Error("…")); // 被忽略setTimeout(() => resolve("…")); // 被忽略 });
这儿的宗旨是,一个被 executor 完成的工作只能有一个结果或一个 error。
并且,
resolve/reject
只需要一个参数(或不包含任何参数),并且将忽略额外的参数。
以
Error
对象 reject如果什么东西出了问题, executor 应该调用
reject
。这可以使用任何类型的参数来完成(就像resolve
一样)。但是建议使用Error
对象(或继承自Error
的对象)。这样做的理由很快就会显而易见。
Resolve/reject 可以立即进行
实际上,executor 通常是异步执行某些操作,并在一段时间后调用
resolve/reject
,但这不是必须的。我们还可以立即调用resolve
或reject
,就像这样:let promise = new Promise(function(resolve, reject) {// 不花时间去做这项工作resolve(123); // 立即给出结果:123 });
例如,当我们开始做一个任务时,但随后看到一切都已经完成并已被缓存时,可能就会发生这种情况。
这挺好。我们立即就有了一个 resolved 的 promise。
state
和result
都是内部的Promise 对象的
state
和result
属性都是内部的。我们无法直接访问它们。但我们可以对它们使用.then
/.catch
/.finally
方法。我们在下面对这些方法进行了描述。
消费者:then,catch,finally
Promise 对象充当的是 executor(“生产者代码”或“歌手”)和消费函数(“粉丝”)之间的连接,后者将接收结果或 error。可以通过使用 .then
、.catch
和 .finally
方法为消费函数进行注册。
then
最重要最基础的一个就是 .then
。
语法如下:
promise.then(function(result) { /* handle a successful result */ },function(error) { /* handle an error */ }
);
.then
的第一个参数是一个函数,该函数将在 promise resolved 后运行并接收结果。
.then
的第二个参数也是一个函数,该函数将在 promise rejected 后运行并接收 error。
例如,以下是对成功 resolved 的 promise 做出的反应:
let promise = new Promise(function(resolve, reject) {setTimeout(() => resolve("done!"), 1000);
});// resolve 运行 .then 中的第一个函数
promise.then(result => alert(result), // 1 秒后显示 "done!"error => alert(error) // 不运行
);
第一个函数被运行了。
在 reject 的情况下,运行第二个:
let promise = new Promise(function(resolve, reject) {setTimeout(() => reject(new Error("Whoops!")), 1000);
});// reject 运行 .then 中的第二个函数
promise.then(result => alert(result), // 不运行error => alert(error) // 1 秒后显示 "Error: Whoops!"
);
如果我们只对成功完成的情况感兴趣,那么我们可以只为 .then
提供一个函数参数:
let promise = new Promise(resolve => {setTimeout(() => resolve("done!"), 1000);
});promise.then(alert); // 1 秒后显示 "done!"
catch
如果我们只对 error 感兴趣,那么我们可以使用 null
作为第一个参数:.then(null, errorHandlingFunction)
。或者我们也可以使用 .catch(errorHandlingFunction)
,其实是一样的:
let promise = new Promise((resolve, reject) => {setTimeout(() => reject(new Error("Whoops!")), 1000);
});// .catch(f) 与 promise.then(null, f) 一样
promise.catch(alert); // 1 秒后显示 "Error: Whoops!"
.catch(f)
调用是 .then(null, f)
的完全的模拟,它只是一个简写形式。
finally
就像常规 try {...} catch {...}
中的 finally
子句一样,promise 中也有 finally
。
.finally(f)
调用与 .then(f, f)
类似,在某种意义上,f
总是在 promise 被 settled 时运行:即 promise 被 resolve 或 reject。
finally
是执行清理(cleanup)的很好的处理程序(handler),例如无论结果如何,都停止使用不再需要的加载指示符(indicator)。
像这样:
new Promise((resolve, reject) => {/* 做一些需要时间的事儿,然后调用 resolve/reject */
})// 在 promise 为 settled 时运行,无论成功与否.finally(() => stop loading indicator)// 所以,加载指示器(loading indicator)始终会在我们处理结果/错误之前停止.then(result => show result, err => show error)
也就是说,finally(f)
其实并不是 then(f,f)
的别名。它们之间有一些细微的区别:
finally
处理程序(handler)没有参数。在finally
中,我们不知道 promise 是否成功。没关系,因为我们的任务通常是执行“常规”的定稿程序(finalizing procedures)。finally
处理程序将结果和 error 传递给下一个处理程序。例如,在这儿结果被从
finally
传递给了then
:new Promise((resolve, reject) => {setTimeout(() => resolve("result"), 2000) }).finally(() => alert("Promise ready")).then(result => alert(result)); // <-- .then 对结果进行处理
在这儿,promise 中有一个 error,这个 error 被从
finally
传递给了catch
:new Promise((resolve, reject) => {throw new Error("error"); }).finally(() => alert("Promise ready")).catch(err => alert(err)); // <-- .catch 对 error 对象进行处理
这非常方便,因为 finally
并不是意味着要处理 promise 的结果。所以它将结果传递了下去。
在下一章中,我们将详细讨论 promise 链以及处理程序(handler)之间的结果传递。
我们可以对 settled 的 promise 附加处理程序
如果 promise 为 pending 状态,
.then/catch/finally
处理程序(handler)将等待它。否则,如果 promise 已经是 settled 状态,它们就会运行:// 下面这 promise 在被创建后立即变为 resolved 状态 let promise = new Promise(resolve => resolve("done!"));promise.then(alert); // done!(现在显示)
请注意这使得 promise 比现实生活中的“订阅列表”方案强大得多。如果歌手已经发布了他们的单曲,然后某个人在订阅列表上进行了注册,则他们很可能不会收到该单曲。实际生活中的订阅必须在活动开始之前进行。
Promise 则更加灵活。我们可以随时添加处理程序(handler):如果结果已经在了,它们就会执行。
接下来,让我们看一下关于 promise 如何帮助我们编写异步代码的更多实际示例。
示例:loadScript
我们从上一章获得了用于加载脚本的 loadScript
函数。
这是基于回调函数的变体,记住它:
function loadScript(src, callback) {let script = document.createElement('script');script.src = src;script.onload = () => callback(null, script);script.onerror = () => callback(new Error(`Script load error for ${src}`));document.head.append(script);
}
让我们用 promise 重写它。
新函数 loadScript
将不需要回调。取而代之的是,它将创建并返回一个在加载完成时解析(resolve)的 promise 对象。外部代码可以使用 .then
向其添加处理程序(订阅函数):
function loadScript(src) {return new Promise(function(resolve, reject) {let script = document.createElement('script');script.src = src;script.onload = () => resolve(script);script.onerror = () => reject(new Error(`Script load error for ${src}`));document.head.append(script);});
}
用法:
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");promise.then(script => alert(`${script.src} is loaded!`),error => alert(`Error: ${error.message}`)
);promise.then(script => alert('Another handler...'));
我们立刻就能发现 promise 相较于基于回调的模式的一些好处:
Promises | Callbacks |
---|---|
Promises 允许我们按照自然顺序进行编码。首先,我们运行 loadScript 和 .then 来处理结果。
|
在调用 loadScript(script, callback) 时,在我们处理的地方(disposal)必须有一个 callback 函数。换句话说,在调用 loadScript 之前,我们必须知道如何处理结果。
|
我们可以根据需要,在 promise 上多次调用 .then 。每次调用,我们都会在“订阅列表”中添加一个新的“分析”,一个新的订阅函数。在下一章将对此内容进行详细介绍:Promise 链
|
只能有一个回调。 |
因此,promise 为我们提供了更好的代码流和灵活性。但其实还有更多相关内容。我们将在下一章看到。
ES6 --- Promise深入学习(二)Promise,then,catch,finally,及使用示例相关推荐
- 十二、Promise的学习笔记(Promise的基本使用、链式编程、all())
一.认识Promise ES6中一个非常重要和好用的特性就是Promise 但是初次接触Promise会一脸懵逼,这TM是什么东西? 看看官方或者一些文章对它的介绍和用法,也是一头雾水. Promis ...
- Promise深度学习---我のPromise/A+实现
原文地址fancierpj0.github.io/iPromise/ 目录 (づ ̄ 3 ̄)づ=> 掘金这目录页..放这么角落..嗯..干撒呢 [TOC] 序 本文会对Promise规范进行一个比 ...
- 彻底掌握 Promise-原生Promise的实现(二) Promise的链式调用
在 彻底掌握 Promise- 原生 Promise 的实现(一) 已经初步实现了 promise 的简易版本,这距离实现完整版本的 Promise 还有一段距离,下面我们将在简易版本的基础上增加 P ...
- ES6基础5(Promise)-学习笔记
文章目录 ES6基础5(Promise)-学习笔记 Promise 三个状态 状态转换 手写Promise源码 同步异步概念 jquery中 串行并行 async-await 微任务 宏任务 ES6基 ...
- 【ES6】阮一峰ES6学习之Promise(一)
Promise 一.含义 1. 概念 2. 特点 3. 基本用法 4. 为什么要用 Promise 1. 指定回调函数的方式更加灵活 2. 支持链式调用,可以解决回调地狱的问题 用Promise实现A ...
- Promise相关学习
仅代表个人学习 1.函数对象与实例对象 <script>function Person() { //首字母大写,这是一个构造函数}//给函数对象添加属性Person.age=18conso ...
- ES6 ------ 基础(三)——Promise 基本使用
Promise 基本使用 Promise 是 ES6 引入的解决异步编程的新解决方案.语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果. Promise 构造函数: ...
- promise async 学习笔记
promise and async js设计初衷是应用于浏览器端,为了避免用户交互时候的冲突,js是单线程的,为了解决js中的很多异步场景的问题. //最常见的一种回调的嵌套:---回调地狱 doAs ...
- ES6中的异步对象Promise
回忆一下ES5中的怎么使用异步方法 // es5中的异步回调let ajax = function(callback){console.log('执行') // 执行setTimeout(() =&g ...
最新文章
- VOICE VOER IP(VoIP)理论(NP水平学的)
- Ajax监测开始执行及结束执行
- 回溯经典(指定位置N皇后问题)
- 不同分支设置不同的远程仓库
- app测试过程和重点关注内容
- 树莓派使用autofs挂载移动硬盘 -----linux通用
- MATLAB与STK互联17:卫星对象操作(8)—地面站可见性计算并获取数据
- (案例篇)日志易:IT运维分析及海量日志搜索的实践之路(下)
- Linux之计划任务
- mentohust配置
- CMM(Capability Maturity Model) 能力成熟度模型
- 【愚公系列】2021年11月 攻防世界-进阶题-MISC-053(很普通的数独)
- 如何画出漂亮的神经网络图?
- 简述MySQL分区表类型
- 商人渡模型matlab及模型假设,商人过河问题
- jQuery过滤器:筛选jquery对象数组中的DOM对象
- centos 日志文件
- 微信搜一搜迈出新的一步,好戏来了
- 如何用Vue+Vuetify快速建站?
- 免费AI标注工具-医疗影像标注工具