持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第26天,点击查看活动详情
前言
promise就像视频博主,每次更新视频都给订阅者发消息提醒类似。好处在于,有新视频可以提醒观看,意外发不了也会有通知。
代码世界
- 生产者代码:会做一些事,需要时间。 (博主)
- 消费者代码:想“生产者代码”完成工作的第一时间就能获得其工作的成果 (订阅者)
- Promise: 将“生产者代码”和“消费者代码”连接起来的JS对象。 (订阅列表)
- “生产者代码”花费它所需的任意长度时间来产出所承诺的结果,而 “promise” 将在它(译注:指的是“生产者代码”,也就是下文所说的 executor)准备好时,将结果向所有订阅了的代码开放。
生产者世界
语法
let promise = new Promise(function(resolve,reject){
// exectutor (生产者代码,"博主")})
解读:
-
new Promise内部的这个函数————executor。当
new Promise被创建,executor 会自动运行。它包含最终应产出结果的生产者代码。按照上面的类比:executor 就是“博主”。 -
内部的
resolve和reject是由 JavaScript 自身提供的回调(executor的回调)。
resolve(value)—— 如果任务成功完成并带有结果value。reject(error)—— 如果出现了 error,error即为 error 对象。
promise对象的内部属性
state—— 最初是"pending",然后在resolve被调用时变为"fulfilled",或者在reject被调用时变为"rejected"。result—— 最初是undefined,然后在resolve(value)被调用时变为value,或者在reject(error)被调用时变为error。
简单的使用
成功的情况
let promise = new Promise(function(resolve,reject){
setTimeout(() => resolve("done"),2000)
})
-
executor 被自动执行且立即调用(通过
new Promise)。 -
executor 接受两个参数:
resolve和reject。这些函数由 JavaScript 引擎预先定义,因此我们不需要创建它们。我们只需要在executor准备好时调用其中之一即可。经过 2秒的“处理”后,executor 调用
resolve("done")来产生结果。这将改变promise对象的状态:
失败的情况
下面则是一个 executor 以 error 拒绝 promise 的示例:
let promise = new Promise(function(resolve, reject) {
// 1 秒后发出工作已经被完成的信号,并带有 error
setTimeout(() => reject (new Error("Whoops!")) , 1000);
});
对 reject(...) 的调用将 promise 对象的状态移至 "rejected":
总结:executor 应该执行一项工作(通常是需要花费一些时间的事儿),然后调用
resolve或reject来改变对应的 promise 对象的状态。
专一的回调
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // 被忽略
setTimeout(() => resolve("…")); // 被忽略
});
- 宗旨是,一个被 executor 完成的工作只能有一个value或一个 error。并且,
resolve/reject只需要一个参数(或不包含任何参数),并且将忽略额外的参数。
消费者世界
- 使用
.then和.catch方法注册消费函数。
.then
语法:
promise.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
);
.then 的第一个参数是一个函数,该函数将在 promise resolved 且接收到结果后执行。
.then 的第二个参数也是一个函数,该函数将在 promise rejected 且接收到 error 信息后执行。
举个例子:
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!"
- 只对error 感兴趣,那么我们可以使用
null作为第一个参数:.then(null, errorHandlingFunction)。或者我们也可以使用.catch(errorHandlingFunction),其实是一样的:
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// .catch(alert) 与 promise.then(null, alert) 一样
promise.catch(alert); // 1 秒后显示 "Error: Whoops!"
.catch(f) 调用是 .then(null, f) 的完全的一致,它只是一个简写形式。
清理
finally的功能是设置一个处理程序在前面的操作完成后,执行清理/终结。 (一个 resolved 或 rejected 的 promise 都会被称为 “settled”。)
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) 的别名。它没有参数且不知道promise是否成功,将结果或 error 的promise结果由下面的处理程序处理
应用
接下来,让我们看一下关于 promise 如何帮助我们编写异步代码的更多实际示例。
这是基于回调函数的变体,记住它:
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 相较于基于回调的模式的一些好处:
| promise | callback |
|---|---|
promise 允许我们按照自然顺序进行编码。首先,我们运行 loadScript 和 .then 来处理结果。 | 在调用 loadScript(script, callback) 时,我们必须有一个 callback 函数可供使用。换句话说,在调用 loadScript 之前,我们必须知道如何处理结果。 |
我们可以根据需要,在 promise 上多次调用 .then。每次调用,我们都会在“订阅列表”中添加一个新的“粉丝”,一个新的订阅函数。 | 只能有一个回调。 |
因此,promise 为我们提供了更好的代码流和灵活性。