promise对象
Promise 对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。Promise 可以让异步操作写起来,就像在写同步操作的流程,而不必一层层地嵌套回调函数。
Promise 是一个对象,也是一个构造函数。
Promise 的设计思想是,所有异步任务都返回一个 Promise 实例。Promise 实例有一个then方法,用来指定下一步的回调函数。1
2var p1 = new Promise(f1);
p1.then(f2);
上面代码中,f1
的异步操作执行完成,就会执行f2
。
Promise 对象的状态
Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。
- 异步操作未完成(pending)
- 异步操作成功(fulfilled)
- 异步操作失败(rejected)
上面三种状态里面,fulfilled
和rejected
合在一起称为resolved
(已定型)。
这三种的状态的变化途径只有两种。
- 从“未完成”到“成功”
- 从“未完成”到“失败”
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。
因此,Promise 的最终结果只有两种。
- 异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。
- 异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。
Promise 构造函数
JavaScript 提供原生的Promise
构造函数,用来生成 Promise 实例。1
2
3
4
5
6
7
8
9var promise = new Promise(function (resolve, reject) {
// ...
if (/* 异步操作成功 */){
resolve(value);
} else { /* 异步操作失败 */
reject(new Error());
}
});
上面代码中,Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己实现。
resolve
函数的作用是,将Promise
实例的状态从“未完成”变为“成功”(即从pending
变为fulfilled
),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。reject
函数的作用是,将Promise
实例的状态从“未完成”变为“失败”(即从pending
变为rejected
),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
1 | function timeout(ms) { |
上面代码中,timeout(100)
返回一个 Promise
实例。100毫秒以后,该实例的状态会变为fulfilled
。
Promise.prototype.then()
Promise 实例的then
方法,用来添加回调函数。
then
方法可以接受两个回调函数,第一个是异步操作成功时(变为fulfilled
状态)的回调函数,第二个是异步操作失败(变为rejected
)时的回调函数(该参数可以省略)。一旦状态改变,就调用相应的回调函数。1
2
3
4
5
6
7
8
9
10
11var p1 = new Promise(function (resolve, reject) {
resolve('成功');
});
p1.then(console.log, console.error);
// "成功"
var p2 = new Promise(function (resolve, reject) {
reject(new Error('失败'));
});
p2.then(console.log, console.error);
// Error: 失败
上面代码中,p1
和p2
都是Promise
实例,它们的then
方法绑定两个回调函数:成功时的回调函数console.log
,失败时的回调函数console.error
(可以省略)。p1
的状态变为成功,p2
的状态变为失败,对应的回调函数会收到异步操作传回的值,然后在控制台输出。
then
的链式应用:1
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
35Promise.resolve("foo")
// 1. 接收 "foo" 并与 "bar" 拼接,并将其结果做为下一个 resolve 返回。
.then(function(string) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
string += 'bar';
resolve(string);
}, 1);
});
})
// 2. 接收 "foobar", 放入一个异步函数中处理该字符串
// 并将其打印到控制台中, 但是不将处理后的字符串返回到下一个。
.then(function(string) {
setTimeout(function() {
string += 'baz';
console.log(string);
}, 1)
return string;
})
// 3. 打印本节中代码将如何运行的帮助消息,
// 字符串实际上是由上一个回调函数之前的那块异步代码处理的。
.then(function(string) {
console.log("Last Then: oops... didn't bother to instantiate and return " +
"a promise in the prior then so the sequence may be a bit " +
"surprising");
// 注意 `string` 这时不会存在 'baz'。
// 因为这是发生在我们通过setTimeout模拟的异步函数中。
console.log(string);
});
// logs, in order:
// Last Then: oops... didn't bother to instantiate and return a promise in the prior then so the sequence may be a bit surprising
// foobar
// foobarbaz
then() 用法辨析
Promise 的用法,简单说就是一句话:使用then方法添加回调函数。但是,不同的写法有一些细微的差别1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 写法一
f1().then(function () {
return f2();
});
// 写法二
f1().then(function () {
f2();
});
// 写法三
f1().then(f2());
// 写法四
f1().then(f2);
下面这四种写法都再用then
方法接一个回调函数f3
。写法一的f3
回调函数的参数,是f2
函数的运行结果。1
2
3f1().then(function () {
return f2();
}).then(f3);
写法二的f3
回调函数的参数是undefined
。1
2
3
4f1().then(function () {
f2();
return;
}).then(f3);
写法三的f3
回调函数的参数,是f2
函数返回的函数的运行结果。1
2f1().then(f2())
.then(f3);
写法四与写法一只有一个差别,那就是f2
会接收到f1()
返回的结果。1
2f1().then(f2)
.then(f3);
then
的返回值
当一个 Promise
完成(fulfilled)或者失败(rejected)时,返回函数将被异步调用(由当前的线程循环来调度完成)。具体的返回值依据以下规则返回。如果 then
中的回调函数:
- 返回了一个值,那么
then
返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
- 没有返回任何值,那么
then
返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为undefined
。
- 抛出一个错误,那么
then
返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
- 返回一个已经是接受状态的 Promise,那么
then
返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
- 返回一个已经是拒绝状态的 Promise,那么
then
返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
- 返回一个未定状态(
pending
)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。
实例:图片加载
1 | var preloadImage = function (path) { |
上面代码中,image
是一个图片对象的实例。它有两个事件监听属性,onload
属性在图片加载成功后调用,onerror
属性在加载失败调用。
上面的preloadImage()
函数用法如下。1
2
3preloadImage('https://example.com/my.jpg')
.then(function (e) { document.body.append(e.target) })
.then(function () { console.log('加载成功') })
上面代码中,图片加载成功以后,onload
属性会返回一个事件对象,因此第一个then()
方法的回调函数,会接收到这个事件对象。该对象的target
属性就是图片加载后生成的 DOM
节点。
微任务
then
的回调函数属于异步任务,一定晚于同步任务执行。
但是,Promise
的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。1
2
3
4
5
6
7
8
9
10
11
12setTimeout(function() {
console.log(1);
}, 0);
new Promise(function (resolve, reject) {
resolve(2);
}).then(console.log);
console.log(3);
// 3
// 2
// 1
上面代码的输出结果是321
。这说明then
的回调函数的执行时间,早于setTimeout(fn, 0)
。因为then是本轮事件循环执行,setTimeout(fn, 0)
在下一轮事件循环开始时执行。
reference:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
Author: damn1t
Link: http://microvorld.com/2019/11/02/JS挖坑/js(3)/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.