告别回调地狱,轻松掌握异步编程:Promise 与 async/await 详解

32 阅读5分钟

前言

在前端开发中,异步编程是绕不开的核心知识点。从早期的回调函数到 ES6 推出的 Promise,再到 ES7 新增的 async/await 语法糖,异步编程的体验不断优化。本文将带你系统梳理 Promise 与 async/await 的核心知识点,帮你搞懂异步编程的实现逻辑。

一、从同步到异步:Promise 的登场

在介绍 Promise 之前,我们先看一个同步的字符串验证函数,直观感受同步代码的局限性。

1. 同步验证的痛点

下面的同步函数用于校验字符串数组是否仅包含英文字符,一旦遇到不符合条件的元素就会立即返回错误:

javascript

运行

const batchValidateSecretsSync = (secretsArray) => {
    const englishOnlyRegex = /^[a-zA-Z]+$/;
    const passedResults = [];
    for (const item of secretsArray) {
        if (typeof item !== 'string') {
            return {
                success: false,
                message: `验证失败:发现非字符串类型的值 "${item}"。`
            };
        }

        if (englishOnlyRegex.test(item)) {
            passedResults.push(item);
        } else {
            return {
                success: false,
                message: `验证失败:字符串 "${item}" 包含非英文字符 (如数字或特殊符号)。`
            };
        }
    }
    return {
        success: true,
        message: "所有秘密字符串均通过验证。",
        results: passedResults
    };
};

同步代码的问题在于会阻塞后续逻辑执行,且无法高效处理多个异步任务。而 Promise 则能很好地解决这些问题,我们可以将上述逻辑改造成异步验证:

javascript

运行

const batchValidateSecrets = (secretsArray) => {
    const createValidationPromise = (inputString) => new Promise((resolve, reject) => {
        const englishOnlyRegex = /^[a-zA-Z]+$/;
        if (typeof inputString !== 'string') {
            reject('错误:输入必须是一个字符串。');
            return;
        }
        if (englishOnlyRegex.test(inputString)) {
            resolve(inputString);
        } else {
            reject(`错误:字符串'${inputString}'只能包含英文字符,不能包含数字、空格或其他特殊字符。`);
        }
    });
    const validationPromises = secretsArray.map(secret => createValidationPromise(secret));
    return Promise.all(validationPromises);
};

二、Promise 核心知识点

1. Promise 的三种状态

Promise 存在三种互斥的状态,且状态一旦改变就无法逆转:

  • pending:进行中,初始状态
  • fulfilled:成功,异步操作完成
  • rejected:失败,异步操作出错

示例代码如下:

javascript

运行

const arr = new Promise(
  (resolve,reject)=>{
   if(false){
    resolve(1)//将promise状态变为fulfilled
   }else{
    reject('错误')//将promise状态变为rejected
   }
  }
)
// 捕获错误
arr.catch(reason => {
    console.error("成功捕获到错误:", reason);
});
console.log(arr)

2. then 与 catch 方法

  • then:可以同时处理成功(fulfilled)和失败(rejected)两种状态,第一个参数接收成功结果,第二个参数接收错误原因
  • catch:专门用于捕获 Promise 链中的错误,若 Promise 未做错误捕获会触发全局报错

javascript

运行

arr.then((result)=>{
  console.log('成功',result)
},(error) =>{
  console.log('失败',error)
})

arr.catch(reason => {
    console.error("成功捕获到错误:", reason);
});

3. Promise 的静态方法

Promise 提供了多个实用静态方法,用于协调多个异步任务:

  1. Promise.all() :接收 Promise 数组,只有所有 Promise 都成功才返回结果数组,只要有一个失败就立即触发错误

    javascript

    运行

    const promise1 = Promise.resolve("请求1成功");
    const promise2 = new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error("请求2失败")), 2000);
    });
    const promise3 = Promise.resolve("请求3成功");
    Promise.all([promise1, promise2, promise3])
      .then((results) => {
        console.log("所有请求成功:", results); 
      })
      .catch((error) => {
        console.log("请求失败:", error.message); // 输出:请求失败:请求2失败
      });
    
  2. Promise.any() :等待第一个成功的 Promise 并返回其结果,仅当所有 Promise 都失败时才抛出 AggregateError

    javascript

    运行

    const promise1 = new Promise((resolve) => setTimeout(() => resolve('第一个成功'), 1000));
    const promise2 = new Promise((resolve) => setTimeout(() => resolve('第二个成功'), 500));
    Promise.any([promise1,promise2]).then((result)=>{
      console.log(result) // 输出:第二个成功
    })
    
  3. Promise.race() :返回第一个完成的 Promise 结果,无论该结果是成功还是失败

    javascript

    运行

    const promise1 = new Promise((resolve, reject) => {
      setTimeout(() => reject("one"), 1000);
    });
    const promise2 = new Promise((resolve, reject) => {
      setTimeout(() => reject("two"), 100);
    });
    Promise.race([promise1, promise2])
      .catch((reason) => {
        console.log("拒绝原因:", reason); // 输出:two
      });
    
  4. Promise.resolve()/Promise.reject() :快速创建成功 / 失败状态的 Promise 实例

    javascript

    运行

    const successPromise = Promise.resolve("操作成功:数据获取完成");
    successPromise.then((result) => {
        console.log("成功结果:", result);
    });
    
    const rejectedPromise = Promise.reject("请求失败:网络超时");
    rejectedPromise.catch((err) => {
        console.log("捕获到错误:", err);
    });
    

除此之外,还有 Promise.try()(统一处理同步 / 异步函数为 Promise)、Promise.withResolvers()(简化 Promise 实例创建)等新增方法。

三、Promise 的核心优势

  1. 解决回调地狱传统回调函数处理多层异步会形成嵌套地狱,代码可读性极差:

javascript

运行

getUser((user) => {
  getOrder(user.id, (order) => {
    getProduct(order.productId, (product) => {
      console.log(product);
    }, (err) => { console.error(err); });
  }, (err) => { console.error(err); });
}, (err) => { console.error(err); });

而 Promise 链式调用可将嵌套转为线性结构:

javascript

运行

getUser()
  .then(user => getOrder(user.id))
  .then(order => getProduct(order.productId))
  .then(product => console.log(product))
  .catch(err => console.error(err));
  1. 集中处理错误Promise 链中任意环节的错误都可通过末尾的 catch 统一捕获,无需为每个异步操作单独处理错误。
  2. 统一异步状态管理固定的三状态机制避免了异步操作的状态混乱,让异步逻辑更可控。
  3. 支持异步并行通过 Promise.all 等方法可高效处理多个并行异步任务,提升执行效率。
  4. 统一同步 / 异步逻辑可通过 Promise.try() 等方法将同步函数转为 Promise 实例,实现同步与异步代码的统一处理。

四、async/await:Promise 的语法糖

1. 基本介绍

async/await 是 ES7 推出的语法糖,基于 Promise 实现,用类同步的写法处理异步逻辑,大幅提升代码可读性。

  • async:修饰函数时,函数返回值会自动封装为 Promise 实例,函数内抛出错误等价于返回 Promise.reject()
  • await:只能在 async 函数内使用,会暂停函数执行,直到后面的 Promise 状态变为 fulfilled 并返回结果;若 Promise 失败则会抛出错误,需用 try/catch 捕获

2. 用法示例

对比 Promise 链式调用和 async/await 写法:

javascript

运行

// Promise 链式调用
getUser()
  .then(user => getOrder(user.id))
  .then(order => getProduct(order.productId))
  .then(product => console.log(product))
  .catch(err => console.error(err));

// async/await 写法
async function getProductInfo() {
  try {
    const user = await getUser();   
    const order = await getOrder(user.id); 
    const product = await getProduct(order.productId);    
    console.log(product);
  } catch (err) {
    console.error(err);   
  }
}
getProductInfo();

async 函数的返回值特性示例:

javascript

运行

async function errorFunc() {
  throw new Error("出错了"); // 等价于 return Promise.reject(new Error("出错了"))
}
async function okFunc() {
  return 'yes' // 等价于 return Promise.resolve('yes')
}
errorFunc().catch(err => console.log(err.message)); // 出错了
okFunc().then(result => console.log(result)); // yes

五、总结

Promise 作为 ES6 异步编程标准,解决了传统回调函数的诸多痛点;而 async/await 则进一步优化了异步代码的写法,让异步逻辑更贴近开发者熟悉的同步思维。