同步代码的困扰
默认状态下,JavaScript写的是同步代码,上面的代码执行完之后才会轮到下面代码的执行,假如你写了一个生成n个素数的网页,当你点击按钮之后,一个函数运行,在同步的状态下,直到你的函数运行完毕,用户不能操作这期间网页上任何东西。
但是,我们希望实现一种效果:
- 通过调用一个函数来启动一个长期运行的操作
- 让函数开始操作并立即返回,这样我们的程序就可以保持对其他事件做出反应的能力
- 当操作最终完成时,通知我们操作的结果。
回调地狱
历史上,JavaScript曾使用回调(callback)的思路来实现
回调函数,回调函数基于事件模型(event model),此处的事件一般是某个对象的状态变化,当监听器监听到变化时,触发的就是回调函数,这种异步操作很容易导致写一大堆一连串的回调函数,这就是经典的“回调地狱”
所以,使用回调的异步成为了历史,JavaScript使用了Promise来处理异步
// 回调地狱示例
generatePrimesCallback(100, (primes1) => {
console.log('First batch:', primes1);
generatePrimesCallback(200, (primes2) => {
console.log('Second batch:', primes2);
generatePrimesCallback(300, (primes3) => {
console.log('Third batch:', primes3);
// 继续嵌套...
});
});
});
Promise与then
Promise 是现代 JavaScript 中异步编程的基础。它是一个由异步函数返回的对象,可以指示操作当前所处的状态。在 Promise 返回给调用者的时候,操作往往还没有完成,但 Promise 对象提供了方法来处理操作最终的成功或失败。
Promise 是现代 JavaScript 异步编程的基石,它提供了更优雅的方式来处理异步操作:
- Promise 是一个表示异步操作的对象
- 它有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)
- Promise 的
.then()方法可以链式调用,避免了回调地狱 - 提供了统一的错误处理机制(
.catch())
Promise是一个类型的对象,一般由fetch返回,在Promise之后,往往还跟着大量的then,乍看之下,好像Promise只是把回调地狱变成了一连串的then,但then的绝妙之处在于它返回的也是promise,你实际上写的是一个类似管道的操作,而不是回调地狱
// 回调地狱示例
generatePrimesPromise(100)
.then(primes1 => {
console.log('First batch:', primes1);
return generatePrimesPromise(200);
})
.then(primes2 => {
console.log('Second batch:', primes2);
return generatePrimesPromise(300);
})
.then(primes3 => {
console.log('Third batch:', primes3);
})
.catch(error => {
console.error('Error:', error);
});
现代化async与await
但前端的追求不仅于此,async和await让异步操作更进一步简化,它允许使用者像编写同步代码一样编写异步代码,只需要注意async和await的传染性即可
Async/Await 其实是是建立在 Promise 之上的语法糖,它让异步代码更接近同步代码的写法:
async关键字声明一个异步函数,该函数总是返回一个 Promiseawait关键字等待一个 Promise 完成并返回结果- 具有传染性:使用
await的函数必须标记为async - 使用 try/catch 进行错误处理,更符合直觉
// async & await
async function fetchUserData(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
const userData = await response.json();
return userData;
} catch (error) {
console.error('Failed to fetch user data:', error);
throw error;
}
}