JavaScript Note 2022.09.02
关于JavaScript中的Promise的认识
1.Promise的诞生
Promise是ES6新增的一个类,能够让开发者更优雅地写复杂的异步任务。
下面是一个层层递进的异步任务:
setTimeout(function () {
console.log("1 minute later");
setTimeout(function () {
console.log("1+3 minutes later");
setTimeout(function () {
console.log("1+3+5 minutes later");
}, 5000);
}, 3000);
}, 1000);
上述代码是一种嵌套格式,深层缩进,既不美观又难以阅读。
于是为解决这一问题,Promise应运而生。
new Promise(function (resolve) {
setTimeout(function () {
console.log("1 second later");
resolve();
}, 1000);
}).then(function () {
setTimeout(function () {
console.log("2 second later");
}, 1000);
}).then(function () {
setTimeout(function () {
console.log("3 second later");
}, 1000);
})
上述代码是一种顺序格式,浅层缩进,美观易阅读。
2.Promise的内部属性
由 new Promise
构造器返回的 promise
对象具有以下内部属性:
state
—— 最初是"pending"
,然后在resolve
被调用时变为"fulfilled/resolved"
,或者在reject
被调用时变为"rejected"
。result
—— 最初是undefined
,然后在resolve(value)
被调用时变为value
,或者在reject(error)
被调用时变为error
。
Promise 对象的 state
和 result
属性都是内部的。我们无法直接访问它们。但我们可以对它们使用 .then
/.catch
/.finally
方法。
①.state
Promise有两种state:pending与settled,其中settled又包含fulfilled与rejected。
②.result
Promise有三种result:undefined、value、error。
补充:
- Promise构造函数中,不调用resolve与reject,则首个then不会执行。
- Promise构造函数中,只调用二者之一便执行那个函数。
- Promise构造函数中,两者都调用,只执行首个被调用的。
- 原因:调用首个之后Promise的状态被更改了,任何状态的更改都是最终的 。
3.Promise的构造函数
let promise = new Promise(function(resolve, reject) {
setTimeout(function () {
//doSth...
resolve("1 second later.");
}, 1000);
}).then(function(value) {
console.log(value);
}, function(error) {
console.log(error);
})
Promise构造函数只有一个参数,是一个函数,被称为executor(执行器 / 起始函数)。
resolve/reject
只需要一个参数(或不包含任何参数),并且将忽略额外的参数。
补充(执行器立马执行):
let p = new Promise((resolve, reject) => {
console.log(1);
resolve();
})
p.then(() => console.log(3))
console.log(2);
new Promise()的时候,Promise的执行器就会立马执行!
但是调用resolve()会触发异步操作,传入的then()方法的函数会被添加到 任务队列 并异步执行。
且这里的任务队列指的是“微任务队列”(Microtask queue)。上述代码输出顺序为1 -> 2 -> 3。
补充(执行器参数名可任意取):
let p = new Promise(function (a, b) {
setTimeout(function () {
b("Hello"); //6 error: Hello
},1000);
}).then(function (result) {
console.log("6 result: " + result);
}, function (error) {
console.log("6 error: " + error);
})
let q = new Promise(function (reject, resolve) {
setTimeout(function () {
resolve("Hello"); //6 error: Hello
},1000);
}).then(function (result) {
console.log("6 result: " + result);
}, function (error) {
console.log("6 error: " + error);
})
执行器函数 参数的名字和实际调用的 resolve() 和 reject() 无关,关键在于顺序
第一个参数对应then中第一个函数
第二个参数对应then中第二个函数
4.Promise的三个方法
Promise 类有 .then()
.catch()
和 .finally()
三个方法 。
new Promise(function (resolve, reject) {
var a = 0;
var b = 1;
if (b == 0) reject("Divide zero");
else resolve(a / b);
}).then(function (value) {
console.log("a / b = " + value);
}).catch(function (err) {
console.log(err);
}).finally(function () {
console.log("End");
});
a / b = 0
End
.catch(func)
调用 等效于.then(null, func)
补充(then、catch、finally是有顺序的):
new Promise((resolve, reject) => {
setTimeout(() => resolve("value"), 2000)
})
.finally(() => alert("Promise ready")) // 先触发
.then(result => alert(result)); // <-- .then 显示 "value"
Promise ready
value
这里
finally
早于then
执行,毕竟finally
并不意味着处理一个 promise 的结果。无论结果是什么,它都是进行常规清理的地方。
5.Promise vs callback
Promise | callback | |
---|---|---|
代码逻辑上 | 顺其自然:拿到结果再思考如何处理 | 尚未开始就得先考虑好拿到结果后如何处理 |
多层异步时 | 顺序格式:缩进简单,赏心悦目 | 嵌套格式:层层缩进,混乱无序 |
6.Promise chain
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
new Promise()中有
resolve
或reject
将promise改为fulfilled
状态或rejected
状态,首个then才会被触发。首个then返回一个promise对象,第二个then是这个promise对象的“首个then”。而第二个then以及后续then也能够被触发说明返回的这个promise对象并非pending
状态,而是fulfilled
状态或rejected
状态。
Promise.prototype.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 变为终态时的回调函数的参数是相同的。
7.Promise.all()
let username = undefined;
let flag = false;
{
//替代Promise.all()的方案
setTimeout(() => {
username = "admin";
doSth();
}, 1000);
setTimeout(() => {
flag = true;
doSth();
}, 2000);
function doSth() {
if(username && flag) {
console.log("All is will.");
console.log(username);
console.log(flag);
//doSth
}
}
}
Promise.all([
new Promise((resolve) => {
setTimeout(() => {
username = "admin";
resolve();
}, 1000);
}),
new Promise((resolve) => {
setTimeout(() => {
setTimeout(() => {
flag = true;
resolve();
})
}, 2000);
})
]).then(() => {
console.log("All is will");
console.log(username);
console.log(flag);
})
参考文档
- Runoob,JavaScript Promise,https://www.runoob.com/js/js-promise.html
- JAVASCRIPT.INFO,Promise,https://zh.javascript.info/promise-basics
- JAVASCRIPT.INFO,Promise chain,https://zh.javascript.info/promise-chaining
- MDN Web Docs,Promise,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
- MDN Web Docs, Promise.prototype.the(),https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/then