同步和异步的对比
同步代码
每一行代码必须等到前一行代码执行完毕后才能继续执行。
console.log('Step 1');
console.log('Step 2');
console.log('Step 3');
以上代码将会依次打印:
Step 1
Step 2
Step 3
异步代码:
某些操作可以在后台运行,同时继续执行其他代码。
console.log('Step 1');
setTimeout(() => {
console.log('Step 2');
}, 1000);
console.log('Step 3');
以上代码将会先打印 Step 1
和 Step 3
,然后在1秒后打印 Step 2
:
Step 1
Step 3
Step 2
使用 Promise
的异步操作
假设我们有一个异步函数 fetchData
,它模拟从服务器获取数据,返回一个 Promise
:
function fetchData(url) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`Data from ${url}`);
}, 2000); // 模拟2秒的网络延迟
});
}
fetchData('https://api.example.com/data')
.then((data) => {
console.log(data);
});
console.log('Fetching data...');
这个代码将先打印 Fetching data...
,然后2秒后打印 Data from https://api.example.com/data
:
Fetching data...
Data from https://api.example.com/data
使用 async
和 await
的异步操作
通过 async
和 await
,我们可以更直观地表达异步操作:
async function getData() {
console.log('Fetching data...');
const data = await fetchData('https://api.example.com/data');
console.log(data);
}
getData();
上面的代码看起来更像同步代码,但实际上是异步的。这里发生了什么?
getData
函数是一个异步函数(用async
关键字声明)。- 当执行到
await fetchData('https://api.example.com/data')
时,getData
函数会暂停,等待fetchData
返回的Promise
完成。 - 在
Promise
完成(即fetchData
返回的数据被解析)之前,getData
函数内部的其他代码不会执行。 - 当
Promise
完成时,await
表达式会返回Promise
的解析值,然后继续执行getData
函数后面的代码。
形象化示例
想象你在看一部电影。你把电影暂停,去拿了一袋爆米花,然后再回来继续看电影。暂停和继续的过程是这样的:
- 暂停电影:
await fetchData('https://api.example.com/data')
暂停函数执行。 - 去拿爆米花:函数等待
Promise
完成。与此同时,你可以做其他事情(比如处理其他异步操作)。 - 返回继续看电影:
Promise
完成,await
返回结果,继续执行函数后面的代码。
为什么使用暂停
使用 await
暂停当前的异步函数有以下几个好处:
- 代码更易读:异步代码看起来更像同步代码,减少了嵌套和回调地狱。
- 避免错误:通过等待
Promise
完成,可以确保在使用异步操作结果时,这些结果是可用的。 - 更好的错误处理:使用
try...catch
可以更方便地捕获异步操作中的错误。
JavaScript 中的异步处理
使用 Promise
处理异步
传统上,JavaScript 使用 Promise
来处理异步操作:
function cookRice() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Rice is ready');
}, 3000); // 假设煮饭需要3秒
});
}
function cookDish() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Dish is ready');
}, 2000); // 假设炒菜需要2秒
});
}
function cookSoup() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Soup is ready');
}, 4000); // 假设煲汤需要4秒
});
}
cookRice().then((message) => {
console.log(message);
return cookDish();
}).then((message) => {
console.log(message);
return cookSoup();
}).then((message) => {
console.log(message);
});
在上面的代码中,我们使用了 Promise
和 then
方法来处理异步操作。虽然这解决了问题,但代码看起来有点复杂,嵌套层次较深。
使用 async
和 await
处理异步
async
和 await
提供了一种更清晰、更直观的方式来处理异步操作。我们可以这样重写上面的例子:
async function cookMeal() {
try {
const rice = await cookRice();
console.log(rice);
const dish = await cookDish();
console.log(dish);
const soup = await cookSoup();
console.log(soup);
} catch (error) {
console.error('Error:', error);
}
}
cookMeal();
解释
-
async
函数:async
关键字用于声明一个异步函数。这个函数会自动返回一个Promise
,即使你没有显式返回一个Promise
。
-
await
表达式:await
关键字只能在async
函数内部使用。await
会暂停async
函数的执行,等待Promise
完成,并返回Promise
的结果。- 在
await
期间,JavaScript 事件循环仍然会处理其他任务(比如用户输入、网络请求等),不会阻塞整个程序
可以具体地探讨一下这个过程,并通过一个完整的示例来说明 async
和 await
是如何工作的。
假设我们在 getData
函数后面添加一个打印语句:
function fetchData(url) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`Data from ${url}`);
}, 2000); // 模拟2秒的网络延迟
});
}
async function getData() {
console.log('Fetching data...');
const data = await fetchData('https://api.example.com/data');
console.log(data);
}
getData();
console.log('This message is printed immediately after calling getData');
执行顺序解析
-
调用
getData
函数:- 首先执行
getData()
,这个调用启动了getData
函数。 getData
函数开始执行,并打印Fetching data...
。
- 首先执行
-
遇到
await
表达式:- 当执行到
const data = await fetchData('https://api.example.com/data')
时,getData
函数会暂停执行,并等待fetchData
返回的Promise
完成。
- 当执行到
-
继续执行其他代码:
- 在
fetchData
的Promise
完成之前,JavaScript 引擎不会等待,而是继续执行后面的代码。 - 因此,
console.log('This message is printed immediately after calling getData')
会立即执行,并打印消息。
- 在
-
fetchData
完成:- 2秒后,
fetchData
返回的Promise
完成,await
表达式返回Promise
的解析值。 getData
函数继续执行,打印data
的内容。
- 2秒后,
Fetching data...
This message is printed immediately after calling getData
Data from https://api.example.com/data
更详细的解释
-
getData
函数启动:console.log('Fetching data...')
执行并打印Fetching data...
。
-
暂停等待
fetchData
:await fetchData('https://api.example.com/data')
会暂停getData
函数的执行,并等待fetchData
返回的Promise
。- 在
fetchData
完成之前,getData
函数的其余部分不会执行。
-
继续执行其他代码:
getData
函数暂停后,JavaScript 引擎继续执行getData
函数后面的代码,打印This message is printed immediately after calling getData
。
-
fetchData
完成:- 2秒后,
fetchData
返回的Promise
完成,await
表达式返回Promise
的解析值。 const data = await fetchData('https://api.example.com/data')
赋值完成,data
变量包含fetchData
的返回值。console.log(data)
执行并打印Data from https://api.example.com/data
。
- 2秒后,
关键点
await
的作用:暂停当前异步函数的执行,等待Promise
完成,并返回结果。其他代码不会受影响,会继续执行。- 代码的执行顺序:在
await
暂停期间,其他同步代码会继续执行,不会阻塞主线程。 - 异步函数的可读性:
async
和await
提供了更直观的异步处理方式,使得异步代码看起来像同步代码,但实际行为仍然是异步的。