Promise四式击鼓
Promise系列导航
1.Promise本质击鼓传花的游戏
2.Promise四式击鼓
3.Promise击鼓传花
4.Promise花落谁家知多少
前言
👨💻👨🌾📝记录学习成果,以便温故而知新
- Promise系列文章时学习VUE的知识准备,所以就归为VUE系列了。根据MDN的描述,应该是“JavaScript 标准内置对象”,特此说明。
- Promise系列文章主要是学习MDN中 Promise的心得体会,MDN地址。
Promise对象滥觞于Promise() constructor或者Promise.resolve()亦或者Promise.reject()。
Promise() 构造函数 MDN的说明:
Promise() 构造函数创建 Promise 对象。它主要用于封装尚未支持 Promise 的基于回调的 API。
Promise.resolve() MDN的说明:
Promise.resolve() 静态方法将给定的值转换为一个 Promise。如果该值本身就是一个 Promise,那么该 Promise 将被返回;如果该值是一个 thenable 对象,Promise.resolve() 将调用其 then()方法及其两个回调函数;否则,返回的 Promise 将会以该值兑现。 该函数将嵌套的类 Promise 对象(例如,一个将被兑现为另一个 Promise 对象的 Promise 对象)展平,转化为单个 Promise 对象,其兑现值为一个非 thenable 值。
Promise.reject() MDN的说明:
Promise.reject() 静态方法返回一个已拒绝(rejected)的 Promise 对象,拒绝原因为给定的参数。
以上的说明表明三者都可以原生性的产生Promise 对象,所以“滥觞”一次就很恰如其分。这种原生性也正如“击鼓传花”游戏的开始击鼓。
Promise() 是构造函数,需要用关键字new来调用,而Promise.resolve()与Promise.reject()是静态方法。
以上说明来自于MDN“JavaScript 参考>JavaScript 标准内置对象>Promise”,而下面要说的async function来自于“JavaScript 参考>语句和声明>async 函数”。
async function MDN的说明:
async 函数是使用async关键字声明的函数。async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。async 和 await 关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用 promise。
async 函数还可以被作为表达式来定义。
以上说明虽然有些晦涩,但是大致也能看出,这也能产生Promise。
一、一击鼓——Promise() constructor
1.语法
Promise()语法:
new Promise(executor)
当通过 new 关键字调用 Promise 构造函数时,它会返回一个 Promise 对象。
executor语法:
function executor(resolveFunc, rejectFunc) {
// 通常,executor
函数用于封装某些接受回调函数作为参数的异步操作,比如AJAX调用
}
resolveFunc 和 rejectFunc 也是函数,你可以给它们任何实际的名称。它们的函数签名很简单:它们接受一个任意类型的参数。
2.代码及说明
由于MDN的描述比较晦涩,且感觉很杂乱,下面就直接上代码,看运行效果好了。
(1)代码段:
//这里用了箭头函数
const promise1 = new Promise((resolveFunc, rejectFunc) => {
console.log("构造函数内:" + (resolveFunc));//这里可以证明确实是函数
resolveFunc("魏紫");
});
const promise2 = new Promise((resolveFunc, rejectFunc) => {
console.log("构造函数内:" + (rejectFunc));//这里可以证明确实是函数
rejectFunc("姚黄");
});
console.log(promise1);
console.log(promise2);
运行结果:
从运行结果看,构造函数里的代码是直接执行的,Promise的状态也是一目了然的。另外有传“姚黄”时,没有后续的处理,所以抛出了异常。
(2)代码段:
const promise1 = new Promise((resolveFunc, rejectFunc) => {
resolveFunc(Promise.resolve("魏紫"));
});
const promise2 = new Promise((resolveFunc, rejectFunc) => {
resolveFunc(Promise.reject("姚黄"));
});
const promise3 = new Promise((resolveFunc, rejectFunc) => {
rejectFunc(Promise.resolve("魏紫"));
});
const promise4 = new Promise((resolveFunc, rejectFunc) => {
rejectFunc(Promise.reject("姚黄"));
});
console.log(promise1);
console.log(promise2);
console.log(promise3);
console.log(promise4);
运行结果:
请注意红圈中与上面的差异。
感觉这里就体现了MDN中所说
请注意,如果你调用 resolveFunc 或 rejectFunc 并传入另一个 Promise 对象作为参数,可以说该 Promise 对象“已解决”,但仍未“敲定(settled)”。
但是感觉又不全对,rejectFunc调用后Promise对象的状态就是“rejected”。
(3)代码段
const promise1 = new Promise((resolveFunc, rejectFunc) => {
setTimeout(() => {
resolveFunc(promise1);
}, 1000);
});
const promise2 = new Promise((resolveFunc, rejectFunc) => {
setTimeout(() => {
rejectFunc(promise2);
}, 1000);
});
console.log(promise1);
console.log(promise2);
运行结果:
结果真如MDN所说
如果resolveFunc被调用时传入了新建的 Promise 对象本身(即它所“绑定”的 Promise 对象),则 Promise对象会被拒绝并抛出一个 TypeError 错误。
不知道 rejectFunc为什么不是TypeError,不知道是否是Uncaught异常先于TypeError发生???
二、二击鼓——Promise.resolve()
1.语法
Promise.resolve(value)
value是要被该 Promise 对象解决的参数。也可以是一个 Promise 对象或一个 thenable 对象。
Promise.resolve() 方法特殊处理了原生 Promise 实例。如果 value 属于 Promise 或其子类,并且 value.constructor === Promise,那么 Promise.resolve() 直接返回 value,而不会创建一个新的 Promise 实例。否则,Promise.resolve() 实际上相当于 new Promise((resolve) => resolve(value)) 的简写形式。
2.代码及说明
还是上代码比较直观一些。
(1)代码段:
Promise.resolve("魏紫").then(
(value) => {
console.log(value); // "魏紫"
},
(reason) => {
// 不会被调用
},
);
//同时满足一下好奇心
console.log(typeof Promise.resolve);//同时满足一下好奇心
console.log(Promise.resolve);//同时满足一下好奇心
console.log(typeof resolveFunc);//同时满足一下好奇心
new Promise((resolveFunc, rejectFunc) => {
console.log(typeof resolveFunc);//同时满足一下好奇心
console.log(resolveFunc);//同时满足一下好奇心
console.log(Promise.resolve == resolveFunc);//同时满足一下好奇心
console.log(Promise.resolve === resolveFunc);//同时满足一下好奇心
});
运行结果:
这段代码的传参是最简单的字符串。
重点说一下满足好奇心的这段代码。多少教程或文档中把resolveFunc写成resolve,让人误以为resolve就是Promise.resolve(),实践说明并不是这样的。
(2)代码段:
const p = Promise.resolve([1, 2, 3]);
p.then((v) => {
console.log(v);
console.log(v[0]); // 1
});
运行结果:
这段代码的传参所谓复杂了一点,是数组。
(3)代码段:
const original = Promise.resolve("魏紫");
const cast = Promise.resolve(original);
cast.then((value) => {
console.log(`值:${value}`);
});
console.log(`original === cast ? ${original === cast}`);
console.log(original);
console.log(cast);
运行结果:
这段代码中用了一个Promise对象作为了参数,结果正如MDN所说:
Promise.resolve() 方法会重用已存在的 Promise 实例。如果它正在解决一个原生的 Promise,它将返回同一 Promise 实例,而不会创建一个封装对象。
大概也是这个意思:
该函数将嵌套的类 Promise 对象(例如,一个将被兑现为另一个 Promise 对象的 Promise 对象)展平,转化为单个 Promise 对象,其兑现值为一个非 thenable 值。
(4)代码段:
const promise = Promise.resolve(Promise.reject("姚黄"));
console.log(promise);
运行结果:
这段代码的传参是也是Promise对象,只不过是“姚黄”,一如既往地抛出了异常。
MDN中还其它更复杂情况的说明,感觉层次太深,就不罗列了。
三、三击鼓——Promise.reject()
1.语法
Promise.reject(reason)
reason是该 Promise 对象被拒绝的原因。
Promise.reject() 实际上相当于 new Promise((resolve, reject) => reject(reason)) 的简写形式。
2.代码及说明
还是直接上代码。
(1)代码段:
const promise = Promise.reject(new Error("姚黄")).then(
() => {
// 不会被调用
},
(error) => {
console.error(error); // Stacktrace
},
);
console.log(promise);
运行结果:
这段代码的传参是个对象,由于then方法的第二个回调函数做了处理,所以并没有像之前代码的运行结果中抛出了异常。之所以输出是红色显示,是console.error的功劳。
(2)代码段:
const p = Promise.resolve("魏紫");
const rejected = Promise.reject(p);
console.log(rejected === p); // false
/*
rejected.then(() => {}, (v) => {
console.log(v === p); // true
});
*/
rejected.catch((v) => {
console.log(v === p); // true
});
console.log(rejected);
运行结果:
这段代码用了一个Promise对象作为参数,结果表明首先没有像Promise.resolve()返回返回同一 Promise 实例,其次参数原封不用传给了then或者catch方法。正如MDN所说:
与 Promise.resolve 不同,Promise.reject 方法不会重用已存在的 Promise 实例。它始终返回一个新的 Promise 实例,该实例封装了拒绝的原因(reason)。
(3)代码段:
const rejected = Promise.reject(Promise.reject("姚黄"));
console.log(rejected);
运行结果:
这段代码运行结果表明有两次Uncaught。
四、四击鼓——async function
1.语法
async function name(param0) {
statements
}
async function name(param0, param1) {
statements
}
async function name(param0, param1, /* … ,*/ paramN) {
statements
}
参数并没有什么特别的,是可变长参数,0个或多个参数。
主要是函数体:
包含函数主体的表达式。可以使用 await 机制。
返回值:
返回值是一个 Promise,这个 promise 要么会通过一个由 async 函数返回的值被解决,要么会通过一个从 async 函数中抛出的(或其中没有被捕获到的)异常被拒绝。
由于async function是独立模块,与类或者对象没有关系,所以称其为“函数”,而不是“方法”;而Promise是类,所以其成员都称其为“方法”。
2.代码及说明
还是直接上代码。
(1)代码段:
//主调函数
function test(){
const p1 = async1();
const p2 = async2();
const p3 = async3();
console.log(p1);
console.log(p2);
console.log(p3);
p1.then(Wz => console.log("魏紫" + Wz), Yh => console.log("姚黄" + Yh));
p2.then(Wz => console.log("魏紫" + Wz), Yh => console.log("姚黄" + Yh));
p3.then(Wz => console.log("魏紫" + Wz), Yh => console.log("姚黄" + Yh));
}
async function async1() {
return "魏紫1";//不含await
}
async function async2() {
return await "魏紫2";
}
async function async3() {
await "魏紫3";
return "魏紫4";
}
运行结果:
运行结果表名果然是产生了Promise,说“四击鼓”是恰当的。但是要注意函数体中有无await对Promise造成的影响。
需要强调的是:await关键字只在 async 函数内有效。如果你在 async 函数体之外使用它,就会抛出语法错误 SyntaxError 。
(2)代码段:
//主调函数
function test(){
async4().catch(() => {}); // 捕捉所有的错误...
}
async function async4() {
const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
const p2 = new Promise((_, reject) => setTimeout(() => reject("2"), 500));
const results = [await p1, await p2]; // 不推荐使用这种方式,请使用 Promise.all 或者 Promise.allSettled
return p2;
}
运行结果:
以上代码及运行结果是为了说明MDN中所说:
1.在 await 表达式之后的代码可以被认为是存在在链式调用的 then 回调中,多个 await 表达式都将加入链式调用的 then 回调中,返回值将作为最后一个 then 回调的返回值。
2.promise 链不是一次就构建好的,相反,promise 链是分阶段构造的,因此在处理异步函数时必须注意对错误函数的处理。
3.在上面代码中,即使在 promise 链中进一步配置了 .catch 方法处理,也会抛出一个未处理的 promise 被拒绝的错误。这是因为 p2 直到控制从 p1 返回后才会连接到 promise 链。
作为对比,看如下代码:
function test48(){
async5().catch(reason => console.log(reason));
}
async function async5() {
return Promise.resolve().then(x => Promise.reject("姚黄"));
}
运行结果:
对比起来看,本人是不认可MDN所说的第3点的内容,本人的观点是await的阻隔造成的。
另外async function模块中使用 async 函数重写 promise 链的内容感觉很有借鉴意义,可参考原文,这里就不啰嗦了。