前言
还记得你第一次写异步代码时的痛苦吗?回调嵌套、逻辑混乱、代码难以维护……尤其是当“订个披萨”都能写出“回调地狱”的时候,你就会明白:只用回调,已经不够用了。
为了解决这些问题,JavaScript 引入了 Promise,并在此基础上发展出了更优雅的 async/await 语法。它们让异步编程更清晰、更简洁、更接近同步代码的写法。
拿吃披萨的例子来说,用Promise和async/await写出来分别是这样的:
// 步骤一:选择披萨口味
function selectToppings() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const toppings = "培根 + 蘑菇";
console.log("已选择披萨口味:" + toppings);
resolve(toppings);
}, 500);
});
}
// 步骤二:制作披萨
function makePizza(toppings) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("披萨正在制作中...");
resolve(toppings);
}, 2000);
});
}
// 步骤三:准备饮料
function prepareDrink() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("饮料准备完成");
resolve();
}, 1000);
});
}
// 步骤四:叫朋友来
function callFriend() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("朋友到了");
resolve();
}, 1000);
});
}
// Promise 链式调用
selectToppings()
.then(toppings => makePizza(toppings))
.then(() => prepareDrink())
.then(() => callFriend())
.then(() => {
console.log("披萨、饮料、朋友都齐了,开饭啦 🍕");
})
.catch(error => {
console.error("出错了:", error);
});
// async await调用
async function orderDinner() {
try {
const toppings = await selectToppings();
await makePizza(toppings);
await prepareDrink();
await callFriend();
console.log("披萨、饮料、朋友都齐了,开饭啦 🍕");
} catch (error) {
console.error("出错了:", error);
}
}
// 执行
orderDinner();
比回调函数可读性高多了吧hhhhhh~
本文将带你从回调函数出发,一步步走向 Promise 与 async/await ,让你彻底告别“回调地狱”,写出更现代、更优雅的异步代码。
Promise
Promise是什么?
Promise 是一个对象,表示一个异步操作的最终完成(或失败)及其结果值。
换句话说,Promise 可以看作是一个容器,里面保存着一个未来才会结束的操作(通常是异步操作)的结果。
就像一个没有兑现的承诺,你不知道这个承诺以后会不会兑现~
Promise 的三种状态
Promise 对象有三种状态:
| 状态 | 含义 |
|---|---|
| pending(等待中) | 初始状态,既没有被兑现,也没有被拒绝 |
| fulfilled(已兑现) | 操作成功完成,调用 resolve() |
| rejected(已拒绝) | 操作失败,调用 reject() |
一旦状态改变,就不会再变(状态不可逆)。
Promise 的基本结构
看着是挺清晰,我们来一个例子看看Promise到底长什么样吧!
const myPromise = new Promise((resolve, reject) => {
// 异步操作
if (操作成功) {
resolve("成功的结果"); // 调用 resolve 代表成功
} else {
reject("失败的原因"); // 调用 reject 代表失败
}
});
首先解析一下Promise的语法: Promise是一个对象,可以用new来新建一个,其中在建立Promise对象的时候,我们要传入一个函数,这个函数包含两个形参,分别是resolve和reject(当然你可以随便取名),而这两个形参不需要你传入任何值,因为JS引擎在执行的时候就传入好了,这两个值分别对应JS引擎传入的两个函数。 第一个值对应着Promise成功时调用的函数,第二个值对应Promise失败时调用的函数。最后,在函数内部,我们可以进行异步操作,操作完成后根据你编写的逻辑,来决定返回resolve还是reject.
最开始刚声明完毕的时候是这个样子:
由于承诺的事情没有结果,所以处于等待状态。
承诺成功履行时,变为<fullfilled>状态:
承诺失败时,变为<rejected>状态:
Promise 的使用方式
.then() 和 .catch()
.then()用于处理成功的结果(即resolve()).catch()用于处理错误(即reject())
myPromise
.then(result => {
console.log("成功:", result);
})
.catch(error => {
console.error("失败:", error);
});
注意: .then()方法中的形参result来自于它上一个Promise返回的结果,也就是resolve('')里面的结果。
.then()
promise.then(onFulfilled, onRejected);
onFulfilled:当 Promise 成功(resolved)时调用的函数,接收一个参数(即成功的结果)onRejected:当 Promise 失败(rejected)时调用的函数,接收一个参数(即失败的原因)
这两个参数都是可选的,不过你通常会传一个成功处理函数(onFulfilled)。
.then() 方法本身会返回一个新的 Promise对象
.catch()
promise.catch(Errfunction) // 当Promise为rejected的时候会触发并执行里面的函数
.catch() 最常见的用途是:
- 捕获 Promise 链中任何
.then()抛出的异常(下方链式调用有详细讲解) - 处理 Promise 被显式
reject() - 防止未处理的 rejected Promise
.finally()
其常见用途是:
其本身也会返回一个Promise,但是它不会接收任何参数,也就是它记不住之前Promise的结果,它无论如何都会执行,如果其本身抛出一个错误,那么就会覆盖原来的Promise的结果。
举个例子
模拟一个异步请求(比如请求数据)
function fetchData() {
return new Promise((resolve, reject) => {
try {
fetch('https://api.weather.gov/gridpoints/OKX/35,35/forecast')
.then(response => response.json())
.then(data => resolve(data.properties.periods[1].shortForecast));
} catch (error) {
reject(error);
}
})
}
function displayData(weather) {
console.log(weather);
}
function displayError(error){
console.log(error)
}
fetchData()
.then(displayData)
.catch(displayError)
PS:这是外网一个网址,能够查询当地的天气,如果你想让这个结果显现,需要挂梯子哦~
Promise 链式调用(Chaining)
你可以通过 .then() 返回一个新的 Promise,从而实现链式调用,避免回调地狱。
function step1(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Step 1:", x);
resolve(x + 1);
}, 1000);
});
}
function step2(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Step 2:", x);
resolve(x * 2);
}, 1000);
});
}
function step3(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Step 3:", x);
resolve(x - 3);
}, 1000);
});
}
step1(5)
.then(step2)
.then(step3)
.then(result => {
console.log("最终结果是:", result);
})
.catch(error => {
console.error("出错了:", error);
});
Plus: 当Promise在某一个环节中产生了rejected状态后,则其后面的.then()方法都不会执行,会直接执行.catch(),抛出错误,.catch()能捕获整个链的错误。
现在你已经非常了解 Promise 的方方面面,包括它的状态、基本结构、链式调用和错误处理机制 。
现在我们要进入 异步编程的“终极进化形态” —— async/await。
什么是 async/await?
async/await是基于Promise的语法糖,它让异步代码看起来更像同步代码,大大提升了代码的可读性和可维护性。
你可以把它看作是编写异步代码的“更优雅的方式”,也就是写的更好看了。嗯...就是这样
async
使用 async 关键字,你可以声明一个函数为 异步函数。它总是返回一个 Promise。
举个例子
async function sayHello() {
return "Hello, async!";
}
sayHello().then(message => console.log(message));
等价于:
function sayHello() {
return Promise.resolve("Hello, async!");
}
输出 sayHello()结果为:
async可以包裹几乎所有函数,而且会让它们的返回值都变成Promise.
await
await 只能在 async 函数中使用,它会 暂停函数的执行,直到一个 Promise 被解决(fulfilled 或 rejected),然后返回 Promise 的结果。
举个例子
async function fetchUser() {
const response = await fetch('https://api.example.com/user/1');
const user = await response.json();
return user;
}
这看起来是不是和同步代码差不多?我们一步步“等待”结果,不用再写 .then() 链了!
用 async/await 改写之前的获取天气状况的例子:
之前利用Promise写的已经比较清晰了,现在我们来用 async/await 把它变得更清晰:
async function fetchData() {
try {
const response = await fetch('https://api.weather.gov/gridpoints/OKX/35,35/forecast');
const data = await response.json();
return data.properties.periods[1].shortForecast;
} catch (error) {
throw error; // 或者 reject(error) 在 Promise 中
}
}
async function main() {
try {
const result = await fetchData();
console.log("拿到的结果是:", result);
} catch (error) {
console.error("出错了:", error);
}
}
main();
是不是一眼就能看懂整个流程?没有 .then() 的嵌套,逻辑平铺直叙,就像写同步代码一样自然!
async/await 的优势
| 特性 | 描述 |
|---|---|
| ✅ 更清晰的逻辑 | 避免了 Promise 链式调用的“缩进地狱” |
| ✅ 更容易调试 | 代码顺序和执行顺序一致 |
| ✅ 更容易处理异常 | 可以用 try/catch 捕获错误 |
| ✅ 更现代 | 是目前 JavaScript 异步编程的主流写法 |
⚠注意
1. async 和 await 必须一起使用
例外情况:JS 模块(ES Modules)和 Chrome 开发者控制台
想使用
await,必须在async函数内部,否则会报错。但在 Chrome 的控制台中(REPL 环境)和 ES 模块的顶级作用域(Top-level await)中可以例外地使用await,无需包裹在async函数中。
2. async/await 只对返回 Promise 的函数生效
await只对返回Promise的函数起作用。如果你await一个普通值(如字符串、数字),它会立刻返回这个值,不会等待。
3. 你可以 await 任何返回 Promise 的函数
不管这个函数是不是
async函数,只要它返回一个Promise,你就可以await它。
4. 任何函数都可以转换为 async 函数
包括普通函数、箭头函数、方法、构造函数等,只要加上
async关键字,它就会变成一个返回Promise的异步函数。
5. 所有 async 函数都返回一个 Promise
所有
async函数的返回值都会被自动封装成一个Promise。即使你return 123,它也会变成Promise.resolve(123)。
6. 使用 try/catch 进行错误处理
在
async/await中,推荐使用try/catch来捕获异步错误,这样可以像同步代码一样处理异常,比.catch()更直观
总结
JavaScript 的异步编程经历了从 回调函数 → Promise → async/await 的不断演进,目的就是为了写出 更清晰、更易维护 的代码。
Promise是一个对象,它包含着一个状态,表示一个结果值,如果成功就调用resolve(),失败就调用reject()
其中Promise包含三个状态:
在Promise还没有结果时,为Pending状态,表示等待
resolve()后变为fulfilled状态
reject()后变为rejected状态
Promise可以使用.then()方法进行下一步:
promise.then(onFulfilled, onRejected);
onFulfilled:当 Promise 成功(resolved)时调用的函数,接收一个参数(即成功的结果)onRejected:当 Promise 失败(rejected)时调用的函数,接收一个参数(即失败的原因)
随后.then()也会返回一个Promise对象
Promise还能进行链式调用:
step1(5)
.then(step2)
.then(step3)
.then(result => {
console.log("最终结果是:", result);
})
.catch(error => {
console.error("出错了:", error);
});
可以不断将上一步的结果传递给下一步,可以用catch()获取reject()传递的错误信息。
顺嘴一说:还有个api叫.finally(),一般放在Promise链的最后,无论传递结果出不出错,这一句都会执行。
async/await是全新的api,是对Promise的优化,我们可以利用async/await代替Promise来执行异步任务。
以上就是全文了,如有错误,希望大家指出呀!拜拜!