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 使用中的陷阱

  1. 多个 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 ,修改后的程序运行效率直接提升一倍。

  1. 不能直接使用 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");
}
  1. 不能在全局中使用 await 关键字
    await 只能在异步函数用有效。如果想要在最外层使用 await ,需要先定义一个异步函数,然后在函数体中使用 await 。
// await someAsyncOperation();

async function f(){
    await someAsyncOperation();
}

// 更简介的写法
(async()=>{
    await someAsyncOperation();
})

参考:

  1. 异步编程:bilibli
  2. jsonplaceholder
  3. Promise:MDN