12. JavaScript 的异步编程之理解 Promise 和 理清 async/await 过程

20 阅读4分钟

JavaScript 的异步编程之理解 Promise 和 理清 async/await 过程

在 JavaScript 中,异步编程 是一种 处理任务 的方式,它允许程序在 执行某个 耗时操作(如 网络请求读取文件 等)时,不会阻塞 后续代码的 执行

常见 异步编程 的 实现方式

1 回调函数(Callbacks)

回调函数 是 异步编程 最基本的 方式。当一个 异步操作 完成时,会 调用 预先定义好的 函数

例如,在浏览器中使用 setTimeout 函数,它接受 两个参数,第一个是 一个 回调函数,第二个是 延迟的 毫秒数。

// 示例代码 1
setTimeout(() => { 
    console.log("延迟后的操作"); 
}, 1000); 
console.log("这行代码会立即执行");

在这个例子中,setTimeout 函数 会在 延迟 1000 毫秒后 执行 回调函数 中的代码。而 console.log("这行代码会立即执行");立即执行,不会等待 setTimeout 中的 回调函数 执行完毕。

2 Promise

Promise 是一种 更现代的 异步处理方式,它代表了一个 异步操作 还未完成 但最终会 返回结果(成功或失败) 的操作。

Promise三种状态pending(进行中)、fulfilled(已成功)、rejected(已失败)。可以通过 回调 .then().catch()处理 成功 或 失败 的结果。

// 示例代码 2
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data received');
    }, 1000);
  });
}

fetchData()
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error(error);
  });

3 async/await + try/catch

asyncawait 是基于 Promise 的 更简洁的 异步编程 语法糖,使得 异步代码 写起来 更像 同步代码

await 用于 等待 Promise 完成 并 返回结果,async 用于 标记 一个函数 为 异步函数,自动返回一个 Promise

// 示例代码 3
 async function getData() {
       try {
           let response = await fetch('https://example.com/api/data');
           let data = await response.json();
           console.log(data);
       } catch (error) {
           console.error(error);
       }
   }
   getData();

在这个例子中,使用 async 标记 异步函数 fetchData,在 异步函数 中,await 关键字 会暂停 异步函数 内 代码的执行,即 当执行到 await fetch(...)await response.json() 时,异步函数 fetchData 的执行会暂停,直到 Promise 已完成(成功或失败),

getData() 函数内部 try{...} 块中 await 修饰的 异步函数 返回的 Promise 状态为失败,或其他代码操作异常,会被内部的 catch{} 块捕获(getData函数 返回的 Promise 状态为 fulfilled)。但如果 catch{} 块中也出现异常,会使得 getData函数 返回的 Promise 状态为 rejected,回调 catch(error) 被调用。

异步编程 的主要优点是 避免了阻塞,使得 应用程序 在等待 外部资源(如 网络请求文件操作)时 可以继续 执行 其他任务。

使用 async/await 优点

  • 同步代码风格asyncawait 组合使用 可以让 异步代码 在形式上更接近 同步代码。当我们使用纯 Promise 时,通过 .then().catch() 链式调用 的方式处理 异步操作,对于复杂的 异步流程,代码会变得 嵌套很深,难以阅读 和 维护。
// 示例代码 4
// 链式调用,嵌套很深
function getData() {
       return fetch('https://example.com/api/data')
          .then(response => response.json())
          .then(data => {
               // 假设这里又需要进行另一个异步操作
               return new Promise((resolve, reject) => {
                   setTimeout(() => {
                       // 对数据进行一些修改
                       let modifiedData = data.map(item => item * 2);
                       resolve(modifiedData);
                   }, 1000);
               });
           })
          .catch(error => console.error(error));
   }
   getData();

// 同步代码风格
async function getData() {
       try {
           let response = await fetch('https://example.com/api/data');
           let data = await response.json();
           await new Promise((resolve) => {
               setTimeout(() => {
                   let modifiedData = data.map(item => item * 2);
                   resolve(modifiedData);
               }, 1000);
           });
           return data;
       } catch (error) {
           console.error(error);
       }
   }
   getData();
  • 集中式错误处理:在 async 函数中,可以使用 try/catch 语句块来 捕获 异步操作中 出现的 错误。这与 同步代码的错误处理方式类似,更加直观。相比之下,使用 Promise 时,每个 .then().catch() 都需要 单独考虑 错误处理。
// 示例代码 5
 async function getData() {
       try {
           let response = await fetch('https://example.com/api/data');
           let data = await response.json();
           // 假设这里可能出现运行时错误,比如数据格式不符合预期
           if (!Array.isArray(data)) {
               throw new Error('Data is not in the expected format');
           }
           return data;
       } catch (error) {
           console.error(error);
       }
   }
   getData();
  • 顺序执行和依赖关系清晰await 关键字 可以确保 异步操作 按照 顺序执行。当一个 异步操作的结果 依赖于 另一个异步操作的完成时,await 能够很好地处理这种 依赖关系。
// 示例代码 6
async function getFirstData() {
       let response = await fetch('https://example.com/api/firstData');
       return await response.json();
   }
   async function getSecondData() {
       let firstData = await getFirstData();
       let response = await fetch('https://example.com/api/secondData?param=' + firstData.id);
       return await response.json();
   }
   getSecondData();