糯麦 NurMai

400-158-5662

糯麦科技

/

新闻资讯

/

技术讨论

/

只需5个知识点彻底掌握Js中Promise

只需5个知识点彻底掌握Js中Promise

原创 新闻资讯

于 2023-08-23 10:03:17 发布

17209 浏览

JS Promise是一种用于处理异步操作的编程模式。它可以解决回调地狱问题,使代码更加清晰和易于阅读。


使用Promise可以创建一个延迟对象,用于表示异步操作。它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。当异步操作完成时,延迟对象的状态会从pending转换为fulfilled或rejected,并且可以获取到对应的结果或错误信息。


首先来看一下 Promise 中最核心的 5 个知识点:

概念:Promise 是 JS 中用于处理异步操作的一种对象。状态:Promise 对象有三种状态:pending(等待状态)、fulfilled(完成状态)以及 reject(拒绝状态)。Promise 只能从 pending 状态变为 fulfilled 状态或 reject 状态,状态一旦改变就不能再变。用法:Promise 对象可以通过构造函数来创建,构造函数接受一个函数作为参数(实参)。这个实参有两个参数,分别是 resolve 和 reject。resolve 和 reject 也是两个函数。方法:then()、catch()、finally()、Promise.all()、Promise.race() 等。错误处理:如果在执行 resolve 的回调时抛出异常,就会进入 reject 状态。catch 方法返回的仍然是一个 Promise 对象,可以继续调用 then 方法。

下面是这 5 点核心知识的思维导图:


1.png


1:三个状态

当 Promise 进入 resolve 或者 reject 状态后,就可以使用 then() 方法或者 catche() 方法进行链式调用。


1.1 pending 等待状态

当创建一个 Promise 时,它首先就会处于这个状态。此时,异步操作还没有完成,仍在进行中。

const promise = new Promise((resolve, reject) => {
  // 这里是异步操作,例如 setTimeout
});


1.2 fulfilled 完成状态

调用 resolve 函数后,就表示异步操作已经完成,这时的 Promise 就时 fulfilled 状态。在完成状态下,可以通过 then 方法获取异步操作的结果。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("操作成功!"); // Promise 进入 fulfilled 状态
  }, 1000);
});

// fulfilled 状态下,可以通过 then 方法获取异步操作的结果
promise.then(result => {
  console.log(result); // 输出: "操作成功!"
});


1.3 reject 拒绝状态

异步操作失败,或者在 Promise 执行过程中抛出了错误时,通过调用 reject 函数可以让 Promise 进入拒绝状态。在拒绝状态下,可以通过 catche 方法捕获错误或异常。


1.3.1 异步操作失败时调用 reject:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("数据加载失败!"); // Promise 进入 reject 状态
  }, 1000);
});
// reject 状态下,可以通过 catch 方法捕获错误或异常
promise.catch(error => {
  console.log(error); // 输出: "数据加载失败!"
});


1.3.2 Promise 执行过程中抛出错误:

const promise = new Promise((resolve, reject) => {
  throw new Error("出现了一个错误!"); // Promise 进入 reject 状态
});
promise.catch(error => {
  console.log(error.message); // 输出: "出现了一个错误!"
});


2. 静态方法

通过上面的几段代码示例不难看出,Promise 对象是通过 Promise 的构造函数创建的。上面示例中的 then()、catch() 和 finally() 都是 Promise.prototype 上的方法。除了原型链上的这三个方法外,Promise 还提供了一些静态方法。


2.1 Promise.all()

Promise.all() 用于处理多个 Promise,在”并行执行异步操作“、”资源预加载“、”事务处理“及”批量处理任务“等场景下都有应用。

Promise.all() 接受一个 Promise 对象数组作为参数,返回的是一个新的 Promise 对象。这就意味着,可以在返回的新的 Promise 对象上调用 then()、catch() 和 finally() 方法,获取到 Promise 成功之后的结果或者是捕获 Promise 执行过程中的异常和错误。

需要注意的是,只有当数组中的所有 Promise 对象全部成功完成(fulfilled)时,Promise.all() 才会成功完成,如果数组中任何一个 Promise 失败(reject),Promise.all() 返回的新的 Promise 对象也会立即失败,并返回第一个失败的 Promise 的错误。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values); // 输出: Array [3, 42, "foo"]
});


2.1.1 并行执行异步操作

比如在一个页面中,页面上展示的数据需要从 3 个 API 接口中获取,只有从这 3 个接口中成功拿到数据后,再渲染页面。

// 使用 fetch API 作为示例,根据实际情况使用其他的 AJAX 工具或库
function fetchHeadlines() {
  return fetch('https://api.example.com/headlines').then(response => response.json());
}
function fetchSportsNews() {
  return fetch('https://api.example.com/sports').then(response => response.json());
}
function fetchEntertainmentNews() {
  return fetch('https://api.example.com/entertainment').then(response => response.json());
}
// 使用 Promise.all() 并行获取所有接口数据
Promise.all([fetchHeadlines(), fetchSportsNews(), fetchEntertainmentNews()])
  .then(results => {
    const [headlines, sports, entertainment] = results;
    // 使用这三个结果来渲染页面...
    console.log(headlines, sports, entertainment);
  })
  .catch(error => {
    // 如果任何一个请求失败,这里会捕获错误
    console.error('There was an error fetching the news:', error);
  });


2.1.2 资源预加载

要预先加载页面上的图片、视频、音频等资源时,可以用 Promise.all() 来完成,以提高用户体验。

function preloadImage(src) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}
const imageSources = [
  'path/to/image1.jpg',
  'path/to/image2.jpg',
  'path/to/image3.jpg'
];
Promise.all(imageSources.map(src => preloadImage(src)))
  .then(images => {
    console.log('所有图片都已加载完成');
    // 进行其他操作,例如将图片添加到页面中
    // images.forEach(img => document.body.appendChild(img));
  })
  .catch(error => {
    console.error('预加载图片时发生错误:', error);
  });

这样做的目的,通常是确保在与图片进行交互之前,所有图片都已全部加载。比如,要在所有图片加载完成之后创建一个图片幻灯片。


2.1.3 事务处理

在数据库的操作中,“事务”是一个较为常见的概念。事务确保一系列操作要么全部执行成功,要么全部不执行。这主要是为了保证数据的完整性和一致性。

比如,在一个银行系统中,其中有两个操作:从一个账户扣款和向另一个账户存款。严格来讲,这两个操作必须都成功,否则,事务应该回滚。

针对这个场景,下面是一段通过 Promise 实现的简易实现:

function debitAccount(accountId, amount) {
  return new Promise((resolve, reject) => {
    // 模拟从账户扣款的异步操作
    setTimeout(() => {
      console.log(`Debited ${amount} from account ${accountId}`);
      resolve('success');
      // 如果扣款失败,可以调用 reject() 来表示操作失败
      // reject(new Error('Debit failed'));
    }, 1000);
  });
}
function creditAccount(accountId, amount) {
  return new Promise((resolve, reject) => {
    // 模拟向账户存款的异步操作
    setTimeout(() => {
      console.log(`Credited ${amount} to account ${accountId}`);
      resolve('success');
      // 如果存款失败,可以调用 reject() 来表示操作失败
      // reject(new Error('Credit failed'));
    }, 1000);
  });
}
function transferFunds(fromAccountId, toAccountId, amount) {
  return Promise.all([
    debitAccount(fromAccountId, amount),
    creditAccount(toAccountId, amount)
  ]);
}
// 使用示例
transferFunds('A123', 'B456', 100)
  .then(() => {
    console.log('Transfer successful');
  })
  .catch(error => {
    console.log('Transfer failed:', error.message);
    // 在真实的场景中,需要回滚之前的操作,比如退款到原账户
  });


2.1.4 批量处理任务

实际开发中有很多批量处理任务的需求,比如批量发送邮件的场景。

首先,定义一个发送邮件的方法 sendEmail,该方法返回一个 Promise。

function sendEmail(user, message) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 模拟邮件发送成功或失败
      const isEmailSent = Math.random() > 0.1; // 90% 的概率发送成功
      if (isEmailSent) resolve(`邮件已发送给 ${user}`);
      else reject(`邮件发送失败给 ${user}`);
    }, 1000);
  });
}

之后,使用 Promise.all() 来批量发送邮件:

onst users = ['user1@example.com', 'user2@example.com', 'user3@example.com'];
const message = '这是一条通知邮件';
const emailPromises = users.map(user => sendEmail(user, message));
const emailPromises = users.map(user =>
  sendEmail(user, message)
    .then(result => ({ success: true, user, result }))
    .catch(error => ({ success: false, user, error }))
);
Promise.all(emailPromises)
  .then(results => {
    const successfulEmails = results.filter(result => result.success);
    const failedEmails = results.filter(result => !result.success);
    console.log(`成功发送 ${successfulEmails.length} 封邮件:`, successfulEmails);
    console.log(`发送失败 ${failedEmails.length} 封邮件:`, failedEmails);
  });

这里有一点需要注意:前面我们提到 Promise.all() 方法会在数组中的某个 Promise 失败后,整个 Promise.all() 都会失败。而这个场景中,我们需要拿到发送成功的和发送失败的邮件数量,就可以通过在每个 Promise 上添加一个 catch() 方法来做到。在 Promise 上添加 catch() 方法后能让 Promise 解析为成功状态。

你可能会有疑问,Promise 上添加一个 catch() 方法后,应该代表这个 Promise 失败(reject)了呀,为什么会被解析成成功(fulfilled)状态呢?

如果你有这个疑问,说明你对 Promise 的 reject 状态理解的还不够。我们回顾一下上面对 reject 状态的描述:

异步操作失败,或者在 Promise 执行过程中抛出了错误时,通过调用 reject 函数可以让 Promise 进入拒绝状态。

关键点在这里:异步操作失败 or 抛出错误。

当我们为一个 Promise 添加 .catch() 方法时,我们实际上是在为这个 Promise 添加了一个错误处理函数。如果这个 Promise 失败(rejected),那么 .catch() 中的函数会被执行。但是,.catch() 本身也会返回一个新的 Promise 对象。

如果 .catch() 中的函数执行成功并返回一个值,或者没有任何返回值,那么由 .catch() 返回的新 Promise 会是一个成功(fulfilled)的状态。如果 .catch() 中的函数抛出一个错误,那么由 .catch() 返回的新 Promise 会是一个失败(rejected)的状态。

因此,当我们在每个 sendEmail Promise 后面添加 .catch() 并返回一个对象时,无论 sendEmail 是否失败,由 .catch() 返回的新 Promise 都会是一个成功的状态,因为我们没有在 .catch() 中抛出任何错误。

这就是为什么说,通过添加 .catch(),我们可以确保每个 Promise 都解析为成功状态。

在上面的示例中,尽管 failingPromise 是一个失败的状态,但由 .catch() 返回的新 Promise 是一个成功的状态,因为我们在 .catch() 中返回了一个值。


2.2 Promise.race()

Promise.race() 是 Promise 的一个静态方法,在需要对多个并发操作进行“竞速”或设置超时时非常有用。

Promise.race() 接受一个 Promise 对象的数组(或其他可迭代对象)作为参数。当数组中的任何一个 Promise 完成(无论是 resolve 还是 reject)时,Promise.race() 返回的 Promise 也会立即完成,并采用第一个完成的 Promise 的状态和值。

Promise.race() 有两个常见的应用场景:竞速和超时控制。


2.2.1 竞速

Promise.race() 可以被视为一个竞速,哪个 Promise 先完成,就采用哪个 Promise 的结果。

比如,我们要从多个源或者服务器请求相同的资源,并且请求返回的资源都是相同的。如果我们想要从响应最快的接口上拿到资源,那么这种场景下,就可以用 Promise.race() 方法。

const urls = [
  'https://server1.example.com/data',
  'https://server2.example.com/data',
  'https://server3.example.com/data'
];
// 请求数据,返回最快的响应
Promise.race(urls.map(url => fetch(url)))
  .then(response => console.log(response))
  .catch(error => console.error(error));


2.2.2 超时控制

假设你有一个异步操作,你希望它在一定的时间内完成,否则就认为它超时。

除了服务端设置请求超时外,我们前端也可以维护一个超时时间。比如,你发送了一个网络请求,但不希望请求过程无限期地等待,这时就可以用 Promise.race() 设置一个超时时间来实现这一需求。

unction fetchWithTimeout(url, timeout) {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Request timed out')), timeout);
  });
  return Promise.race([fetchPromise, timeoutPromise]);
}
fetchWithTimeout('https://api.example.com/data', 5000)
  .then(data => console.log(data))
  .catch(error => console.error(error));

在这个示例中,如果 fetch 请求在 5 秒内没有完成,timeoutPromise 会被 reject,导致整个 Promise.race() 被 reject。


2.3 其他静态方法

除 Promise.all() 和 Promise.race() 外,Promise 还提供了两个静态方法:Promise.allSettled() 和 Promise.any() 方法。

这两个方法我就不在这里过多赘述了,它们的用法都很简单,你完全可以作为扩展知识去了解它们。这里推荐去看 MDN 的文档,非常详细:

MDN - Promise 文档:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise


3. 小结

今天我们主要分享了 Promise 的三个状态和两个常用的静态方法。你可以先不管 Promise 上的静态方法,但是 Promise 的三种状态是必须要知道并且掌握的。

JavaScript

Promise

同步操作

阅读排行

  • 1. 几行代码就能实现Html大转盘抽奖

    大转盘抽奖是网络互动营销的一种常见形式,其通过简单易懂的界面设计,让用户在游戏中体验到乐趣,同时也能增加商家与用户之间的互动。本文将详细介绍如何使用HTML,CSS和JavaScript来实现大转盘抽奖的功能。

    查看详情
  • 2. 浙江省同区域公司地址变更详细流程

    提前准备好所有需要的资料,包含:房屋租赁合同、房产证、营业执照正副本、代理人身份证正反面、承诺书(由于我们公司其中一区域已有注册另外一公司,所以必须需要承诺书)

    查看详情
  • 3. 微信支付商户申请接入流程

    微信支付,是微信向有出售物品/提供服务需求的商家提供推广销售、支付收款、经营分析的整套解决方案,包括多种支付方式,如JSAPI支付、小程序支付、APP支付H5支付等支付方式接入。

    查看详情
  • 4. 阿里云域名ICP网络备案流程

    根据《互联网信息服务管理办法》以及《非经营性互联网信息服务备案管理办法》,国家对非经营性互联网信息服务实行备案制度,对经营性互联网信息服务实行许可制度。

    查看详情
  • 5. 微信小程序申请注册流程

    微信小程序注册流程与微信公众号较为相似,同时微信小程序支持通过已认证的微信公众号进行注册申请,无需进行单独认证即可使用,同一个已认证微信公众号可同时绑定注册多个小程序。

    查看详情