JavaScript中的异步编程
1. js 中两种异步方式(单线程编程语言)
1.1 传统回调函数(Callback Function)
例如:setTimeOut
- 回调函数的缺点:函数回调地狱 ,一个函数执行完毕在执行内部的另外一个,一层一层的嵌套。
setTimeout(() => {
console.log("等三秒后");
setTimeout(() => {
console.log("再等三秒后");
setTimeout(() => {
console.log("又等三秒后");
console.log("hello")
}, 3000);
}, 3000);
}, 3000);
- 单线程优点: 由于所有操作运行在一个线程中,无需考虑线程同步和资源竞争的开销,避免了线程之间的频繁切换和竞争问题,降低了开销。
1.2 Promise(承诺)
promise 承诺请求会在未来某一个时刻返回。
- 解决了回调地狱的问题
- 例如:fetch 向服务器请求,返回 Promise 对象动态更新页面内容。
fetch("https://jsonplaceholder.typicode.com/post/1")
.then((response) => response.json())
.then((data) => console.log(data));
-
Promise 的链式调用避免了代码的层层嵌套,即便是我们有一个很长的链,代码也不过是下下方增长而不是向右,因此可读性会提升很多。
-
如果请求中出现错误,会触发 catch ,then 将不回执行。
fetch("https://jsonplaceholder.typicode.com/post/1")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.log(error));
- finally 方法在 Promise 链结束之后调用,无论失败与否,用来执行清理操作。
fetch("https://jsonplaceholder.typicode.com/post/1")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.log(error))
.finally(() => {
// 执行清理等操作
});
1.3 async、await 语法糖
async 与 await 是基于 Promise 之上的语法糖,可以让异步操作更加简洁明了。
首先使用 async 修饰异步函数,异步函数是指返回值为 Promise 对象的函数。被 async 修饰的 function 接收请求必须用 await 。
async function f(){
const response = await fetch("https://jsonplaceholder.typicode.com/post/1");
const json = await response.json();
console.log(json);
}
f(); // 这个函数返回值永远是一个 Promise
await 虽然看上去会暂停函数的执行,但在等待的过程中,JavaScript 同样可以处理其他的任务。
这是因为 await 底层是基于 Promise 和事件循环机制实现的。
1.4 await 使用中的陷阱
- 多个 await 会打破并行
// async function f(){
// const a = await fetch("https://jsonplaceholder.typicode.com/post/1");
// const b = await fetch("https://jsonplaceholder.typicode.com/post/2");
// }
async function f(){
const PromiseA = fetch("https://jsonplaceholder.typicode.com/post/1");
const PromiseB = fetch("https://jsonplaceholder.typicode.com/post/2");
const [a,b] = await Promise.all([PromiseA, PromiseB]);
}
可以使用 Promise.all 将所有的 Promise 组合起来,然后在进行 await ,修改后的程序运行效率直接提升一倍。
- 不能直接使用 forEach 或 map 这一类方法
尽管在回调函数中写了 await ,但是 forEach 会立刻返回,不会暂停等到所有的异步操作都执行完毕。
如果希望等待循环中的异步操作都一一完成之后才继续执行,应当使用传统的 for 循环。
async function f(){
// [1,2,3].forEach(async (i)=>{
// await someAsyncOperation();
// })
for (const i of [1,2,3]) {
await someAsyncOperation();
}
console.log("done");
}
- 不能在全局中使用 await 关键字
await 只能在异步函数用有效。如果想要在最外层使用 await ,需要先定义一个异步函数,然后在函数体中使用 await 。
// await someAsyncOperation();
async function f(){
await someAsyncOperation();
}
// 更简介的写法
(async()=>{
await someAsyncOperation();
})
参考: