🚊 Promise 的基本使用
# 理解 Promise
从抽象角度看:
Promise 是一门 ES6 规范下新的技术;
Promise 是 JavaScript 中进行异步编程的新解决方案;
从具体角度看:
- 从语法上来讲: Promise 是一个构造函数;
- 从功能上来讲: Promise 对象用来封装一个异步操作并且可以获取其成功 / 失败的结果值。
理解例子:
假如你是一名歌手,你向粉丝承诺会在(单曲发布)的第一时间发给他们。你给了粉丝们一个列表。他们可以在上面填写他们的电子邮件地址,以便当歌曲发布后,(单曲发布成功)让所有订阅了的人能够立即收到。即便遇到不测,例如录音室发生了火灾,以致你无法发布新歌,(单曲发布失败)他们也能及时收到相关通知。
# Promis 语法
Promise 的构造器语法:
let promise = new Promise(function(resolve, reject) {
// executor
});
2
3
当
new Promise
被创建,executor 会自动运行。它包含最终应产出结果的生产者代码。它的参数resolve
和reject
是由 JavaScript 自身提供的回调。要执行的代码仅在 executor 的内部。当 executor 获得了结果,无论是早还是晚都没关系,它应该调用以下回调之一:
resolve(value)
:如果任务成功完成并带有结果value
。reject(error)
:如果出现了错误,error
为错误对象。
简而言之:executor
会自动运行并尝试执行一项工作。尝试结束后,如果成功则调用 resolve
,如果出现错误则调用 reject
。
# Promise 的简单使用案例
🌰 例子 / 定时器:
const btn = document.querySelector('#btn');
// 绑定单击事件
btn.addEventListener('click', function () {
/*setTimeout(() => {
// 获取从一到一百的随机数
let n = rand(1, 100);
// 判断
if (n <= 30) {
alert('中奖')
} else {
alert('再接再厉')
}
}, 1000)*/
// Promise 实现
const p = new Promise((resolve, reject) => {
// 成功时调用resolve,失败时调用reject(函数类型)
//包含一个函数
setTimeout(() => {
// 获取从一到一百的随机数
let n = rand(1, 100);
// 判断
if (n <= 30) {
resolve(n) // 将Promise对象状态设置为「成功」
} else {
reject(n) // 将Promise对象状态设置为「失败」
}
}, 1000)
})
// 第一个是成功时调用,第二个是失败时调用
// 在构造p时传给状态的值,不同状态分别为 value / reason
p.then((value) => {
alert('成功中奖,中奖数字为' + value)
}, (reason) => {
alert('再接再厉,当前号码为'+ reason)
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
🌰 例子 / 前文 加载脚本 例子:
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);
});
}
2
3
4
5
6
7
8
9
10
11
在加载脚本完成后返回一个新的 Promise 对象。所以在外部可以使用 处理程序 处理 成功或者失败时的结果。
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");
promise.then(
script => console.log(`${script.src} is loaded`),
error => console.log(`Error: ${error.massage}`)
)
promise.then(
script => console.log("... another handler")
)
2
3
4
5
6
7
8
9
10
# 对比 Promise 与 回调模式
Promis | Callbacks |
---|---|
Promises 允许按照自然顺序进行编码。 | 在调用要执行的函数 之前,必须知道如何处理结果(设定好回调)。 |
可以根据需要,可以有多个处理结果的函数。(Promise 链) | 只能有一个回调。 |
# Promise 的状态
Promise 状态(PromiseState)是 Promise 实例对象的一个属性,有三个值: pending
(初始状态)、 resolved
/ fulfilled
、 rejected
。
从上面简单使用 Promise 的例子中,可以看到 Promise 可以有两种转变:
pending
➡️resolved
pending
➡️rejected
注意,Promise 只有这两种状态,并且每一个 Promise 对象 只能转变一次,无论转为成功还是失败,都会有一个与状态对应的结果:
# Promise 的结果
在 resolve(value)
被调用时(成功状态 fulfilled
)结果为 value
;在 reject(error)
被调用时(失败状态 rejected
) 结果为 error
。( resolve
和 rejecet
只能有一个参数)
提示
resolve
/reject
可以立即进行。虽然通常excutor
是异步执行某些操作,并在一段时间(经过判断)之后调用resolve
和reject
,但这不是必须的。也可以立即调用resolve
和reject
。let promise = new Promise(function(resolve, reject) { // 不花时间去做这项工作 resolve(123); // 立即给出结果:123 });
1
2
3
4状态和结果(
state
和result
) 是内部的。无法直接访问它们。但可以对它们使用.then
/.catch
/.finally
方法。
# .then
/ .catch
/ .finally
Promise 对象中是 执行函数(生产者代码 / 歌手) 与 消费函数(粉丝)之间的连接。消费函数将接收 结果(成果或者出错),可以通过使用 .then
、 .catch
、 .finally
为消费函数注册。
# .then
promise.then{
// 处理成功的结果的消费函数
function(result) { /* ... */ }
// 处理出错结果的消费函数
function(error) { /* ... */ }
}
2
3
4
5
6
7
🌰 例子:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve 运行 .then 中的第一个函数
promise.then(
result => alert(result), // 1 秒后显示 "done!"
error => alert(error) // 不运行
);
2
3
4
5
6
7
8
9
当回调中出错时:
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!"
);
2
3
4
5
6
7
8
9
一般 .then
中可以只放处理成功时的结果的函数:
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then( result => alert(result) );
// 简化为
promise.then(alert)
2
3
4
5
6
7
# .catch
.catch
一般用于处理出错的结果 error
:
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
promise.catch(error => alert(error))
// 简化为:
// .catch(error) 与 promise.then(null, error) 一样
promise.catch(alert); // 1 秒后显示 "Error: Whoops!"
2
3
4
5
6
7
8
(相当于 .then(null, error)
)
# .finally
就像常规 try {...} catch {...}
中的 finally
子句一样,Promise 中也有 finally
。
.finally(f)
调用与 .then(f, f)
类似,在某种意义上, f
总是在 Promise 状态被确定时运行:即 Promise 被 resolve 或 reject。
finally
是执行清理(cleanup)的很好的处理程序(handler),例如无论结果如何,都停止使用不再需要的加载指示符(indicator)。
🌰 例子:
new Promise((resolve, reject) => {
/* ... */
})
// 在 promise 为 settled 时运行,无论成功与否
.finally(() => stop loading indicator)
// 所以,加载指示器(loading indicator)始终会在我们处理结果/错误之前停止
.then(result => show result, err => show error)
2
3
4
5
6
7
提示
.finally
与 .then
的区别:
finally
处理程序(handler)没有参数。在finally
中,不必知道 Promise 成功出错与否,finally
执行的是常规的 定稿程序(finalizinig procedures)。finally
将结果和出错(value 和 error) 传递给下一个处理程序:new Promise((resolve, reject) => { setTimeout(() => resolve("result"), 2000) }) .finally(() => alert("Promise ready")) .then(result => alert(result)); // <-- .then 对结果进行处理
1
2
3
4
5new Promise((resolve, reject) => { throw new Error("error"); }) .finally(() => alert("Promise ready")) .catch(err => alert(err)); // <-- .catch 对 error 对象进行处理
1
2
3
4
5因为
finally
并不是意味着要处理 Promise 的结果,所以它将结果传递了下去。
.then
/ .catch
/ .finally
三者为 Promise 状态确定后附加的处理程序。如果 Promise 为 pending 状态,这三种处理程序(handler)将等待它;否则,如果 Promise 已经是 settled 状态,它们就会运行。
这给 Promise 带来更加灵活的特性。因为可以随时添加处理程序,如果结果已经存在了,它们就会执行。
# 总结
# Promise 的基本流程
分析上述基本使用例子,得出 Promise 的基本流程:
# Promise 的特点
指定回调函数的方式更加灵活:
在以前,回调函数必须在启动异步任务前指定。
Promise 下的流程为:启动异步任务 ➡️ 返回 Promise 对象 ➡️ 给 Promise 对象绑定回调函数 (甚至可以在异步任务将结束后指定多个)
支持链式调用,可以解决「回调地狱」的问题:
「回调地狱」:回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件。回调地狱带来了 代码不便于阅读、不便与异常处理的缺点。使用 Promise 链式调用解决。
终极的解决方案:
async / await