学习Promise与异步编程

Promise 基础

Promise 生命周期

每个 Promise 都会经历一个短暂的生命周期,初始为挂起态( pending state ),这表示异步操作尚未结束。一个挂起的 Promise 也被认为是未决的( unsettled )。一旦异步操作结束, Promise 就会被认为是已决的( settled ),并进入两种可能状态之一:

  1. 已完成( fulfilled ): Promise 的异步操作已成功结束;
  2. 已拒绝( rejected ): Promise 的异步操作未成功结束,可能是一个错误,或由其他原因导致。

创建未决的 Promise

新的 Promise 使用 Promise 构造器来创建。此构造器接受单个参数:一个被称为执行器( executor )的函数,包含初始化 Promise 的代码。该执行器会被传递两个名为 resolve()reject() 的函数作为参数。 resolve() 函数在执行器成功结束时被调用,用于示意该 Promise 已经准备好被决议( resolved ),而 reject() 函数则表明执行器的操作已失败。

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
// Node.js 范例
let fs = require("fs");
function readFile(filename) {
return new Promise(function(resolve, reject) {
// 触发异步操作
fs.readFile(filename, { encoding: "utf8" }, function(err, contents) {
// 检查错误
if (err) {
reject(err);
return;
}
// 读取成功
resolve(contents);
});
});
}
let promise = readFile("example.txt");
// 同时监听完成与拒绝
promise.then(function(contents) {
// 完成
console.log(contents);
}, function(err) {
// 拒绝
console.error(err.message);
});

创建已决的 Promise

Promise.resolve()

Promise.resolve() 方法接受单个参数并会返回一个处于完成态的 Promise 。这意味着没有任何作业调度会发生,并且你需要向 Promise 添加一个或更多的完成处理函数来提取这个参数值。

1
2
3
4
let promise = Promise.resolve(42);
promise.then(function(value) {
console.log(value); // 42
});

Promise.reject()

也可以使用 Promise.reject() 方法来创建一个已拒绝的 Promise 。此方法像Promise.resolve() 一样工作,区别是被创建的 Promise 处于拒绝态,如下:

1
2
3
4
let promise = Promise.reject(42);
promise.catch(function(value) {
console.log(value); // 42
});

非 Promise 的 Thenable

当一个对象拥有一个能接受 resolvereject 参数的 then() 方法,该对象就会被认为是一个非 Promisethenable ,就像这样:

1
2
3
4
5
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};

调用 Promise.resolve() 来将 thenable 转换为一个已完成的 Promise

1
2
3
4
5
6
7
8
9
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});

使用 Promise.resolve() ,同样还能从一个 thenable 创建一个已拒绝的 Promise

1
2
3
4
5
6
7
8
9
let thenable = {
then: function(resolve, reject) {
reject(42);
}
};
let p1 = Promise.resolve(thenable);
p1.catch(function(value) {
console.log(value); // 42
});

执行器错误

如果在执行器内部抛出了错误,那么 Promise 的拒绝处理函数就会被调用。

1
2
3
4
5
6
let promise = new Promise(function(resolve, reject) {
throw new Error("Explosion!");
});
promise.catch(function(error) {
console.log(error.message); // "Explosion!"
});

全局的 Promise 拒绝处理

Promise 最有争议的方面之一就是:当一个 Promise 被拒绝时若缺少拒绝处理函数,就会静默失败。

Node.js 的拒绝处理

Node.js 中, process 对象上存在两个关联到 Promise 的拒绝处理的事件:

  1. unhandledRejection :当一个 Promise 被拒绝、而在事件循环的一个轮次中没有任何拒绝处理函数被调用,该事件就会被触发;
  2. rejectionHandled :若一个 Promise 被拒绝、并在事件循环的一个轮次之后再有拒绝处理函数被调用,该事件就会被触发。

unhandledRejection 事件处理函数接受的参数是拒绝原因(常常是一个错误对象)以及已被拒绝的 Promise

1
2
3
4
5
6
7
8
let rejected;
process.on("unhandledRejection", function(reason, promise) {
console.log(reason.message); // "Explosion!"
console.log(rejected === promise); // true
});
rejected = Promise.reject(new Error("Explosion!"));

rejectionHandled 事件处理函数则只有一个参数,即已被拒绝的 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let rejected;
process.on("rejectionHandled", function(promise) {
console.log(rejected === promise); // true
});
rejected = Promise.reject(new Error("Explosion!"));
// 延迟添加拒绝处理函数
setTimeout(function() {
rejected.catch(function(value) {
console.log(value.message); // "Explosion!"
});
}, 1000);

为了正确追踪潜在的未被处理的拒绝,使用 rejectionHandledunhandledRejection 事件就能保持包含这些 Promise 的一个列表,之后等待一段时间再检查此列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let possiblyUnhandledRejections = new Map();
// 当一个拒绝未被处理,将其添加到 map
process.on("unhandledRejection", function(reason, promise) {
possiblyUnhandledRejections.set(promise, reason);
});
process.on("rejectionHandled", function(promise) {
possiblyUnhandledRejections.delete(promise);
});
setInterval(function() {
possiblyUnhandledRejections.forEach(function(reason, promise) {
console.log(reason.message ? reason.message : reason);
// 做点事来处理这些拒绝
handleRejection(promise, reason);
});
possiblyUnhandledRejections.clear();
}, 60000);

浏览器的拒绝处理

浏览器同样能触发两个事件,来帮助识别未处理的拒绝。这两个事件会被 window 对象触发,并完全等效于 Node.js 的相关事件:

浏览器事件的处理函数则只会接收到包含下列属性的一个对象:

  • type : 事件的名称( "unhandledrejection""rejectionhandled" );
  • promise :被拒绝的 Promise 对象;
  • reasonPromise 中的拒绝值(拒绝原因)。

浏览器的实现中存在的另一个差异就是:拒绝值( reason )在两种事件中都可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let rejected;
window.onunhandledrejection = function(event) {
console.log(event.type); // "unhandledrejection"
console.log(event.reason.message); // "Explosion!"
console.log(rejected === event.promise); // true
};
window.onrejectionhandled = function(event) {
console.log(event.type); // "rejectionhandled"
console.log(event.reason.message); // "Explosion!"
console.log(rejected === event.promise); // true
};
rejected = Promise.reject(new Error("Explosion!"));

以下代码在浏览器中追踪未被处理的拒绝,与 Node.js 的代码非常相似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let possiblyUnhandledRejections = new Map();
// 当一个拒绝未被处理,将其添加到 map
window.onunhandledrejection = function(event) {
possiblyUnhandledRejections.set(event.promise, event.reason);
};
window.onrejectionhandled = function(event) {
possiblyUnhandledRejections.delete(event.promise);
};
setInterval(function() {
possiblyUnhandledRejections.forEach(function(reason, promise) {
console.log(reason.message ? reason.message : reason);
// 做点事来处理这些拒绝
handleRejection(promise, reason);
});
possiblyUnhandledRejections.clear();
}, 60000);

串联 Promise

每次对 then()catch() 的调用实际上创建并返回了另一个 Promise ,仅当前一个 Promise 被完成或拒绝时,后一个 Promise 才会被决议。

1
2
3
4
5
6
7
8
9
10
11
12
13
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value) {
console.log(value);
}).then(function() {
console.log("Finished");
});
// 输出
// 42
// Finished

捕获错误

1
2
3
4
5
6
7
8
9
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value) {
throw new Error("Boom!");
}).catch(function(error) {
console.log(error.message); // "Boom!"
});

响应多个 Promise

Promise.all() 方法

Promise.all() 方法接收单个可迭代对象(如数组)作为参数,并返回一个 Promise 。这个可迭代对象的元素都是 Promise ,只有在它们都完成后,所返回的 Promise 才会被完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
let p3 = new Promise(function(resolve, reject) {
resolve(44);
});
let p4 = Promise.all([p1, p2, p3]);
p4.then(function(value) {
console.log(Array.isArray(value)); // true
console.log(value[0]); // 42
console.log(value[1]); // 43
console.log(value[2]); // 44
});

若传递给 Promise.all() 的任意 Promise 被拒绝了,那么方法所返回的 Promise 就会立刻被拒绝,而不必等待其他的 Promise 结束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
reject(43);
});
let p3 = new Promise(function(resolve, reject) {
resolve(44);
});
let p4 = Promise.all([p1, p2, p3]);
p4.catch(function(value) {
console.log(Array.isArray(value)) // false
console.log(value); // 43
});

Promise.race() 方法

Promise.race() 提供了监视多个 Promise 的一个稍微不同的方法。此方法也接受一个包含需监视的 Promise 的可迭代对象,并返回一个新的 Promise ,但一旦来源 Promise 中有一个被解决,所返回的 Promise 就会立刻被解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let p1 = Promise.resolve(42);
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
let p3 = new Promise(function(resolve, reject) {
resolve(44);
});
let p4 = Promise.race([p1, p2, p3]);
p4.then(function(value) {
console.log(value); // 42
});

传递给 Promise.race()Promise 确实在进行赛跑,看哪一个首先被解决。若胜出的 Promise 是被完成,则返回的新 Promise 也会被完成;而胜出的 Promise 若是被拒绝,则新 Promise 也会被拒绝。