Promise 是什么?有什么用途?

2023年2月9日

💎 加入 E+ 成長計畫 如果你喜歡我們的內容,歡迎加入 E+,獲得更多深入的軟體前後端內容

Promise 是在 ES6 出现的新功能,并且是用来优化过去回调函式 callback 的写法。这篇面试详解中会讨论到, promise 是什么、为什么需要有 Promise,以及延伸到 async/await 相关问题。

为什么要使用 Promise?

在探讨 Promise 之前,我们先来看一下为什么需要它的出现。

JavaScript 中有一个重要概念 - 异步 (async),它允许我们在执行耗时任务时,不必等待程式完成,而是继续执行下面的代码,直到任务完成再通知。常用的异步操作有:文件操作、数据库操作、AJAX 以及定时器等。

JavaScript 有两种实现异步的方式:

第一种:回调函式 callback function

在 ES6 promise 出现之前,通常使用回调函式 (callback) 实现异步操作。但使用回调函式 callback 存在一个明显的缺点,当需要执行多个异步操作时,代码会不断往内嵌套,这种情况通常被称为「callback 地狱」(callback hell)。

callback(() => {
  console.log("Hello!");
  callback(() => {
    console.log("Hello!");
    callback(() => {
      console.log("Hello!");
      callback(() => {
        console.log("Hello!");
      }, 200);
    }, 200);
  }, 200);
}, 200);

而为了解决这种问题,就出现了第二种方法 - promise。

什么是 Promise?

上一段提到 Promise 出现的原因,这一段我们来看那到底 Promise 是什么。

Promise 照英文上的意思,是约定、承诺,它代表的意涵是这个约定请求会在未来每个时刻返回数据给调用者。在 MDN 文件中, Promise 是用来表示一个异步操作的最终完成(或失败)及其结果值

怎么使用 Promise

Promise 是一个构造函式,我们需要透过 new 关键字建立一个 Promise。而 Promise 会接收一个函式作为参数,这个函式又称为 executor,executor 会立即执行。如下方代码,若丢入浏览器开发者工具执行,console 的结果会立刻被打印出来。

new Promise((resolve, reject) => {
  console.log("executor 立即执行"); // executor 立即执行
});

而 executor 函式,会再接受另外两个函式参数

  • resolve 实现函式:如下方代码,请求成功的例子,正确的时候会调用 resolve 函式,并回传结果。
  • reject 拒绝函式:如下方代码,请求失败的例子,失败的时候会调用 reject 函式,并回传结果。
function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === "explainthis.io") {
        resolve("hello welcome to explainthis");
      } else {
        reject("it is not explainthis");
      }
    }, 3000);
  });
}

// 1. 请求成功
requestData("explainthis.io").then((res) => {
  console.log(res); //hello welcome to explainthis
});

//2. 请求失败
requestData("explainthis.com").catch((e) => console.log(e)); //it is not explainthis

Promise 的状态

一个 Promise 一定会处于以下三种状态的其中一种

  1. pending:初始状态,执行了 executor,但还在等待中。
  2. fulfilled:表示操作完成,执行 resolve 函式。
  3. rejected:表示操作失败,执行 reject 函式。

then 的使用

  1. 多次调用

延续前段谈到的,异步用第一种 callback 做法很容易有 callback hell 的产生,而使用 Promise 的好处则可以避免这种难以阅读的写法。

Promise 可以用一种链式 (chaining) 的方式将这些异步操作串连,如下方代码范例,我们可以透过 then 来将等完成之后的操作串起。

function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === "explainthis.io") {
        resolve("hello welcome to explainthis");
      } else {
        reject("it is not explainthis");
      }
    }, 3000);
  });
}

requestData("explainthis.io")
  .then((res) => {
    console.log(res); //hello welcome to explainthis
    return 1;
  })
  .then((res) => {
    console.log(res); // 1
    return 2; //hello welcome to explainthis
  })
  .then((res) => {
    console.log(res); // 2
  });
  1. then 方法可以接受两个参数,一个为成功的回调,另一个为失败的回调
function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === "explainthis.io") {
        resolve("hello welcome to explainthis");
      } else {
        reject("it is not explainthis");
      }
    }, 0);
  });
}

requestData("explainthis.com").then(
  (res) => {
    console.log(res);
  },
  (reason) => {
    console.log(reason);
  }
);

//it is not explainthis

错误处理

Promise 的一个好处是错误处理,最简单的方式是在加上一个 catch 来捕捉错误,并执行一些错误处理代码。如下方代码,如果请求失败,例如由于网络故障,则 Promise 会被拒绝。在这种情况下,catch 方法将捕获错误,并输出错误讯息。

fetch("https://explainthis.com/data")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error("oops!", error);
  })
  .finally(() => {
    console.log("close loader");
  });

finally 方法

如果有加上 finally,那 Promise 状态不论是 fulfilled 还是 rejected 都会需要执行 finally 方法。 finally 是 Promise 处理流程中一个非常有用的方法,它可以帮助我们在不管 Promise 是否成功的状态下,执行一定必要的操作。

使用情境例如,一进入页面要先从服务器 fetch 资料,等待的同时会显示 loading 的画面,而最后不论是否有拿到资料,我们都需要把 loader 关闭。这时候,关闭 loader 的逻辑,就很适合放在 finally 中。如下方代码:

fetch("https://explainthis.com/data")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error(error);
  })
  .finally(() => {
    console.log("close loader");
  });

什么是 async/await?

async/await 是一种基于 Promise 之上的语法糖,比 Promise 的写法更像是同步操作。

首先,我们会使用 async 关键字将函式标记为异步函式,异步函式就是指返回值为 Promise 物件的函式。

在异步函式中我们可以调用其他的异步函式,不过不是使用 then(),而是使用 await 语法,await 会等待 Promise 完成之后直接返回最终的结果。

async function getData() {
  const res = await fetch("https://getsomedata");
  const data = await res.json();
  console.log(data);
}

getData();

相关文章

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們