【H2O2 | 软件开发】什么是Promise?

目录

前言

开篇语

准备工作

正文

概述

三种状态

创建和使用

链式操作

多对象处理

语法糖

回调地狱和优化

结束语


前言

开篇语

本系列为短篇,每次讲述少量知识点,无需一次性灌输太多的新知识点。该主题文章主要是围绕前端、全栈开发相关面试常见问题撰写的,希望对诸位有所帮助。

如果您需要为面试八股文做准备,笔者建议重点关注加粗强调部分,它们是概念中的关键词。

准备工作

软件:【参考版本】Visual Studio Code

系统版本: Win10/11

正文

概述

Promise是JavaScript中用于处理异步操作的对象,它有三种状态:​Pending(待定)​Fulfilled(已兑现)​ 和 ​Rejected(已拒绝)​。Promise通过then()catch()方法处理成功和失败的结果,支持链式调用,可以避免回调地狱。它还提供了静态方法,如Promise​​​​​​.all()Promise​​​​​​.race()来处理多个异步操作,Promise.resolve()Promise.reject()来分别处理成功和失败的返回结果。Promise拥有async/await语法糖,让异步代码更易读和易维护。 

三种状态

一个Promise对象有三种状态:

  • Pending(待定)​:初始状态,在创建Promise对象时为该状态。
  • Fulfilled(已兑现)​:表示操作成功完成,在调用Promise.resolve()方法后为该状态。
  • Rejected(已拒绝)​:表示操作失败,在调用Promise.reject()方法后为该状态。

创建和使用

和其他对象一样,Promise实例可以通过new Promise()创建,该实例接受一个执行器函数(executor function)作为参数,该函数有两个参数: resolvereject

一个最简单的使用Promise处理异步操作的示例如下——

const myPromise = new Promise((resolve, reject) => {
  // res为异步操作的结果
  if (res) {
    resolve('success');
  } else {
    reject('fail');
  }
});

有时,我们需要获得异步操作的结果,无论它是成功还是失败。这时,resolve()和reject()方法的参数就可以作为结果返回。

Promise使用then()接收成功的结果,使用catch()接收失败的结果。而then()的参数与resolve()的参数对应,catch()的参数与reject()的参数对应。

myPromise
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error);
  });

链式操作

Promise支持链式操作,这是因为then()方法可以返回一个新的Promise对象,我们可以在下一个then()方法中处理上一步的结果。

使用return来向后面的then()返回参数。

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

myPromise
  .then((result) => {
    console.log(result); // 输出: 1
    return result + 1;
  })
  .then((result) => {
    console.log(result); // 输出: 2
  });

多对象处理

Promise还提供了一些静态方法来处理多个Promise对象:

  • Promise.resolve():立刻返回一个已兑现的Promise。
  • Promise.reject():立刻返回一个已拒绝的Promise。
  • Promise.all():等待所有Promise完成,或者其中一个Promise失败。
  • Promise.race():等待第一个Promise完成或失败。

前两个比较好理解,这里主要结合例子来讲讲后两个方法。

all()和race()的参数列表均为数组,该数组的成员均为Promise对象。

假如我们有两个Promise对象,为了方便查看输出结果,我们分别为这两个对象设置序号作为它们的返回值。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
}); 
const promise2 = Promise.resolve(2);

对于all()方法来说,下面的代码可以输出包含两个结果的数组,因为二者均执行完毕了。

Promise.all([promise1, promise2])
  .then((results) => {
    console.log(results); // 输出: [1, 2]
  })
  .catch((error) => {
    console.error(error);
  });

对于race()方法来说,同样结构的代码,由于2的输出并不需要进行一秒的等待,所以promise2要早于promise1完成,因此只会输出promise2的结果。

Promise.race([promise1, promise2])
  .then((result) => {
    console.log(result); // 输出: 2
  })
  .catch((error) => {
    console.error(error);
  });

语法糖

async/await是Promise的语法糖,它的主要作用是简化异步代码的写法,使其看起来更像同步代码,从而避免回调地狱(Callback Hell)复杂的Promise链式调用。 

async关键字用于声明一个异步函数。在函数前加上async关键字,函数会隐式返回一个Promise。如果函数返回值不是Promise,JavaScript 会自动将其包装成一个已兑现的Promise;如果函数抛出异常,会返回一个已拒绝的Promise。

await关键字用于等待一个Promise完成(兑现或拒绝),只能在async函数中使用。它会暂停async函数的执行,直到Promise完成,然后返回Promise的结果。如果Promise被拒绝,await会抛出异常,可以通过try/catch捕获。

回调地狱和优化

回调地狱(Callback Hell)是指多个嵌套的回调函数导致代码难以阅读和维护的情况。

下面的代码模拟了多次使用异步操作连续调用回调函数输出数据的情况,其中下一次的数据需要在1秒钟之后拼接至上一次的数据之后,并在拼接时打印每一次的结果。

可以看到在多次地调用callback()之后,整段代码变得极其冗长,难以阅读。

function fetchData(callback) {
  setTimeout(() => {
    const data1 = "数据1";
    console.log(data1);
    callback(data1, (data2) => {
      setTimeout(() => {
        console.log(data2);
        callback(data2, (data3) => {
          setTimeout(() => {
            console.log(data3);
          }, 1000);
        });
      }, 1000);
    });
  }, 1000);
}

fetchData((data1, callback) => {
  const data2 = data1 + " -> 数据2";
  callback(data2, (data3) => {
    const data3 = data2 + " -> 数据3";
    callback(data3);
  });
});

这时,我们就可以使用async/await来解决上面的问题。

我们并不需要一次性将所有的回调写入fetchData()方法中,而是是每次在打印数据的同时,将这一次的结果包装在Promise对象中返回。

function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data1 = "数据1";
      console.log(data1);
      resolve(data1);
    }, 1000);
  });
}

利用await关键字可以等待上一步的Promise对象(或者说数据),并为下一步所用。 

async function handleFun() {
  try {
    const data1 = await fetchData("数据1");
    const data2 = await fetchData(data1 + " -> 数据2");
    const data3 = await fetchData(data2 + " -> 数据3");
  } catch (error) {
    console.error("出错:", error);
  }
}

handleFun();

 

结束语

本期内容到此结束。关于本系列的其他博客,可以查看我的面试题专栏。

本系列的博客主要是记录学习经历,并总结阶段的知识点。全篇的操作过程由笔者完成并核验,在部分定义上有参考其他的内容。希望这对您有所帮助,并真心地祝您早日找到心仪的工作岗位。

==期待与你在下一期博客中再次相遇==

——燃尽的【H2O2】

 

你可能感兴趣的:(【H2O2】全栈面试题,前端,javascript,ecmascript6)