注:本文是学习事件循环后的个人笔记,建议配合以下参考资料一起阅读。
资料来自
- 使用 Promise - JavaScript | MDN
- 和 Claude 的互动式问答的结果
什么是Promise
首先需要明确什么是异步操作:异步操作是指不会立刻返回结果的操作,比如网络请求需要时间,JavaScript 不能傻等,会继续执行后面的代码,等结果回来了再处理。
Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。 它接受一个回调函数,称之为executor,参数为resolve和reject,前者用于修改当前promise对象为成功状态(fulfilled),后者则是修改为失败状态(rejected)。
修改为成功状态
const promise = new Promise((resolve, reject) => {
resolve()
})
修改为失败状态
const promise = new Promise((resolve, reject) => {
reject()
})
为什么有 Promise
使用Promises会带来两个好处
- 避免回调地狱
- Promise创建和Promise处理的解耦
接下来会详细说明
回调地狱
promise提供了一种链式的结构,用于解决之前js存在的因为多层回调嵌套导致的回调地狱情况
doSomething(function (result) {
doSomethingElse(result, function (newResult) {
doThirdThing(newResult, function (finalResult) {
console.log(`得到最终结果:${finalResult}`);
}, failureCallback);
}, failureCallback);
}, failureCallback);
随着嵌套层级和代码复杂度的提升,阅读难度会很高。
使用promise,则能改写成
doSomething()
.then((result) => doSomethingElse(result))
.then((newResult) => doThirdThing(newResult))
.then((finalResult) => {
console.log(`得到最终结果:${finalResult}`);
})
.catch(failureCallback);
Promise改写了返回值的传递方式——原版是通过嵌套来层层传递,Promise 是通过返回值来传递。可以看到通过promise的改写,降低了嵌套的层级,提升了代码的可读性,即使有很多的.then,阅读顺序也是向下,且每次处理的操作是很清晰的。
Promise创建和Promise处理的解耦
若采用旧式的回调写法,则创建和处理是立刻发生的,无法做到先创建,然后在指定的时间处理
// 成功的回调函数
function successCallback(result) {
console.log("音频文件创建成功:" + result);
}
// 失败的回调函数
function failureCallback(error) {
console.log("音频文件创建失败:" + error);
}
// 传入处理成功的函数和失败的函数,然后直接执行
createAudioFileAsync(audioSettings, successCallback, failureCallback);
采用Promise,则能实现二者的解耦
const audioPromise = createAudioFileAsync(audioSettings);
// 渲染 UI、处理别的操作等
// 可以在任意时刻、任意地方绑定处理函数
function successCallback(result) {
console.log("音频文件创建成功:" + result);
}
// 失败的回调函数
function failureCallback(error) {
console.log("音频文件创建失败:" + error);
}
audioPromise.then(successCallback, failureCallback);
如何使用 Promise
.then
.then是对Promise对象的具体操作函数,他接受两个回调函数,分别为onFulfilled和onRejected——前者用于处理fulfilled的Promise对象,后者则是rejected的Promise对象。
.then支持链式写法,它接受一个promise,根据promise状态处理它,然后包装成新的Promise传递给下一个.then
例
doSomething()
.then((url) => fetch(url))
.then((res) => res.json())
.then((data) => {
listOfIngredients.push(data);
})
.then(() => {
console.log(listOfIngredients);
});
.catch 与错误处理
.then具有穿透性,若一条.then链没有用于处理rejected的Promise对象,则会让错误一路传递下去,最后静默消失。
为了避免这种情况,引入了catch。它是用于处理rejected的Promise对象的函数,fulfilled 的 Promise 会跳过 .catch(),直接传给下一个 .then()。catch处理后,会返回fulfilled的Promise对象,但若未能处理,则错误会继续向下穿透。
注:catch的写法等价于.then(null, fn)
例:catch处理掉错误后
Promise.reject("出错了")
.catch((err) => {
console.log("捕获到错误:", err); // 捕获到错误:出错了
// 正常执行完,没有抛出新错误
})
.then(() => {
console.log("链继续执行了"); // 这里会执行
});
输出
捕获到错误:出错了
链继续执行了
例:catch未能处理错误
Promise.reject("出错了")
.catch((err) => {
throw new Error("catch里又出错了");
})
.then(() => {
console.log("这里不会执行");
})
.catch((err) => {
console.log("这里才会执行:", err.message); // 这里才会执行:catch里又出错了
});
.all
用于同时执行多个异步函数,但其中一个失败则全部失败。.then() 收到的参数是一个数组,顺序跟传入的顺序一致,不管哪个先完成。
Promise.all([func1(), func2(), func3()])
.then(([result1, result2, result3]) => {
/* 使用 result1、result2 和 result3 */
});
.allSettled
也是同时用于处理多个异步函数,但区别在于任意一个失败都不会导致失败。返回一个数组,需要手动解析每一个Promise情况。
Promise.allSettled([func1(), func2(), func3()]).then((results) => {
// results 是一个数组,每项长这样:
// { status: "fulfilled", value: ... } // 成功
// { status: "rejected", reason: ... } // 失败
});
async/await
async/await是用于简化Promise和then的语法糖,他能用同步的写法来写异步函数。
其中外层函数用async关键字标记为异步函数,函数体则可使用await关键字,使用await标记的异步函数会自动拆开 Promise,拿到里面的值。
以之前的doSomething函数为例
doSomething()
.then((url) => fetch(url))
.then((res) => res.json())
.then((data) => {
listOfIngredients.push(data);
})
.then(() => {
console.log(listOfIngredients);
});
改写成async/await的写法后则是
async function logIngredients() {
const url = await doSomething();
const res = await fetch(url);
const data = await res.json();
listOfIngredients.push(data);
console.log(listOfIngredients);
}
这种写法还带来了可共享作用域的好处:Promise.then的写法中每一个.then的作用域是隔离的,而改写后则可共享。
时序
new Promise 的 executor 是同步执行的,会立刻执行,而.then是微任务
例
const promise = new Promise((resolve, reject) => {
console.log("Promise 执行函数");
resolve();
}).then((result) => {
console.log("Promise 回调(.then)");
});
setTimeout(() => {
console.log("新一轮事件循环:Promise(已完成)", promise);
}, 0);
console.log("Promise(队列中)", promise);
输出
Promise 执行函数
Promise(队列中)Promise {<pending>}
Promise 回调(.then)
新一轮事件循环:Promise(已完成)Promise {<fulfilled>}
事件循环/任务队列的内容可参考:事件循环入门:单线程语言如何实现多线程编程