JavaScript Promise

摘要:在本教程中,您将了解 JavaScript Promise 及其有效使用方式。

为什么使用 JavaScript Promise

以下示例定义了一个函数getUsers(),它返回一个用户对象列表

function getUsers() {
  return [
    { username: 'john', email: '[email protected]' },
    { username: 'jane', email: '[email protected]' },
  ];
}Code language: JavaScript (javascript)

每个用户对象都有两个属性:usernameemail

要从 getUsers() 函数返回的用户列表中按用户名查找用户,您可以使用 findUser() 函数,如下所示

function findUser(username) {
  const users = getUsers();
  const user = users.find((user) => user.username === username);
  return user;
}Code language: JavaScript (javascript)

findUser() 函数中

  • 首先,通过调用 getUsers() 函数获取用户数组
  • 其次,使用 find() 方法在 users 数组上查找具有特定 username 的用户。
  • 第三,返回匹配的用户。

以下是查找用户名为 'john' 的用户的完整代码

function getUsers() {
  return [
    { username: 'john', email: '[email protected]' },
    { username: 'jane', email: '[email protected]' },
  ];
}

function findUser(username) {
  const users = getUsers(); 
  const user = users.find((user) => user.username === username);
  return user;
}

console.log(findUser('john'));
Code language: JavaScript (javascript)

输出

{ username: 'john', email: '[email protected]' }Code language: CSS (css)

findUser() 函数中的代码是同步阻塞的。findUser() 函数执行 getUsers() 函数以获取用户数组,调用 users 数组上的 find() 方法搜索具有特定用户名的用户,并返回匹配的用户。

实际上,getUsers() 函数可能会访问数据库或调用 API 来获取用户列表。因此,getUsers() 函数将会有延迟。

为了模拟延迟,您可以使用 setTimeout() 函数。例如

function getUsers() {
  let users = [];

  // delay 1 second (1000ms)
  setTimeout(() => {
    users = [
      { username: 'john', email: '[email protected]' },
      { username: 'jane', email: '[email protected]' },
    ];
  }, 1000);

  return users;
}Code language: JavaScript (javascript)

工作原理。

  • 首先,定义一个数组 users 并将其值初始化为空数组。
  • 其次,在 setTimeout() 函数的回调中将用户的数组分配给 users 变量。
  • 第三,返回 users 数组

getUsers() 无法正常工作,始终返回空数组。因此,findUser() 函数无法按预期工作

function getUsers() {
  let users = [];
  setTimeout(() => {
    users = [
      { username: 'john', email: '[email protected]' },
      { username: 'jane', email: '[email protected]' },
    ];
  }, 1000);
  return users;
}

function findUser(username) {
  const users = getUsers(); // A
  const user = users.find((user) => user.username === username); // B
  return user;
}

console.log(findUser('john'));
Code language: JavaScript (javascript)

输出

undefinedCode language: JavaScript (javascript)

因为 getUsers() 返回空数组,所以 users 数组为空(A 行)。在 users 数组上调用 find() 方法时,该方法返回 undefined(B 行)

挑战在于如何在一秒钟后访问从 getUsers() 函数返回的 users。一种经典方法是使用回调

使用回调来处理异步操作

以下示例在 getUsers()findUser() 函数中添加了回调参数

function getUsers(callback) {
  setTimeout(() => {
    callback([
      { username: 'john', email: '[email protected]' },
      { username: 'jane', email: '[email protected]' },
    ]);
  }, 1000);
}

function findUser(username, callback) {
  getUsers((users) => {
    const user = users.find((user) => user.username === username);
    callback(user);
  });
}

findUser('john', console.log);Code language: JavaScript (javascript)

输出

{ username: 'john', email: '[email protected]' }Code language: CSS (css)

在本例中,getUsers() 函数接受回调函数作为参数,并在 setTimeout() 函数中使用 users 数组调用它。此外,findUser() 函数接受一个回调函数来处理匹配的用户。

回调方法非常有效。但是,它使代码更难理解。此外,它增加了带有回调参数的函数的复杂性。

如果函数数量增加,您可能会遇到回调地狱问题。为了解决这个问题,JavaScript 提出 Promise 的概念。

了解 JavaScript Promise

根据定义,Promise 是一个对象,它封装了异步操作的结果。

Promise 对象有一个状态,可以是以下状态之一

  • 等待中
  • 已完成,带有
  • 由于原因而被拒绝

在开始时,Promise 的状态是等待中,表示异步操作正在进行。根据异步操作的结果,状态将更改为已完成或拒绝。

已完成状态表示异步操作已成功完成

JavaScript Promise Fulfilled

拒绝状态表示异步操作失败。

创建 Promise

要创建 Promise 对象,可以使用 Promise() 构造函数

const promise = new Promise((resolve, reject) => {
  // contain an operation
  // ...

  // return the state
  if (success) {
    resolve(value);
  } else {
    reject(error);
  }
});Code language: JavaScript (javascript)

Promise 构造函数接受一个回调函数,该函数通常执行异步操作。此函数通常称为执行器。

反过来,执行器接受两个回调函数,分别名为 resolvereject

请注意,传递给执行器的回调函数仅按照惯例称为 resolvereject

如果异步操作成功完成,执行器将调用 resolve() 函数,将 Promise 的状态从等待中更改为已完成,并带有值。

如果发生错误,执行器将调用 reject() 函数,将 Promise 的状态从等待中更改为拒绝,并带有错误原因。

一旦 Promise 达到已完成或拒绝状态,它将保持该状态,无法再进入其他状态。

换句话说,Promise 不能从 已完成 状态变为 拒绝 状态,反之亦然。此外,它也不能从 已完成拒绝 状态返回到 等待中 状态。

创建新的 Promise 对象后,其状态为等待中。如果 Promise 达到 已完成拒绝 状态,则它已解析

请注意,在实践中您很少会创建 Promise 对象。相反,您将使用库提供的 Promise。

使用 Promise:then、catch、finally

1) then() 方法

要获取 Promise 在已完成时产生的值,可以调用 Promise 对象的 then() 方法。以下是 then() 方法的语法

promise.then(onFulfilled,onRejected);Code language: CSS (css)

then() 方法接受两个回调函数:onFulfilledonRejected

如果 Promise 已完成,then() 方法将使用值调用 onFulfilled();如果 Promise 已拒绝,则使用错误调用 onRejected()

请注意,onFulfilledonRejected 参数都是可选的。

以下示例演示如何使用 getUsers() 函数返回的 Promise 对象的 then() 方法

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([
        { username: 'john', email: '[email protected]' },
        { username: 'jane', email: '[email protected]' },
      ]);
    }, 1000);
  });
}

function onFulfilled(users) {
  console.log(users);
}

const promise = getUsers();
promise.then(onFulfilled);Code language: JavaScript (javascript)

输出

[
  { username: 'john', email: '[email protected]' },
  { username: 'jane', email: '[email protected]' }
]Code language: JavaScript (javascript)

在本例中

  • 首先,定义 onFulfilled() 函数,该函数在 Promise 已完成时被调用。
  • 其次,调用 getUsers() 函数以获取 Promise 对象。
  • 第三,调用 Promise 对象的 then() 方法并将用户列表输出到控制台。

为了使代码更简洁,您可以使用箭头函数 作为 then() 方法的参数,如下所示

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([
        { username: 'john', email: '[email protected]' },
        { username: 'jane', email: '[email protected]' },
      ]);
    }, 1000);
  });
}

const promise = getUsers();

promise.then((users) => {
  console.log(users);
});
Code language: JavaScript (javascript)

因为 getUsers() 函数返回 Promise 对象,所以您可以使用 then() 方法将函数调用链接起来,如下所示

// getUsers() function
//...

getUsers().then((users) => {
  console.log(users);
});
Code language: JavaScript (javascript)

在本例中,getUsers() 函数始终成功。要模拟错误,我们可以使用 success 标志,如下所示

let success = true;

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (success) {
        resolve([
          { username: 'john', email: '[email protected]' },
          { username: 'jane', email: '[email protected]' },
        ]);
      } else {
        reject('Failed to the user list');
      }
    }, 1000);
  });
}

function onFulfilled(users) {
  console.log(users);
}
function onRejected(error) {
  console.log(error);
}

const promise = getUsers();
promise.then(onFulfilled, onRejected);Code language: JavaScript (javascript)

工作原理。

首先,定义 success 变量并将其值初始化为 true

如果 successtrue,则 getUsers() 函数中的 Promise 将使用用户列表完成。否则,它将使用错误消息拒绝。

其次,定义 onFulfilledonRejected 函数。

第三,从 getUsers() 函数获取 Promise 并使用 onFulfilledonRejected 函数调用 then() 方法。

以下是使用箭头函数作为 then() 方法的参数的方法

// getUsers() function
// ...

const promise = getUsers();
promise.then(
  (users) => console.log,
  (error) => console.log
);Code language: JavaScript (javascript)

2) catch() 方法

如果只想在 Promise 状态为拒绝时获取错误,可以使用 Promise 对象的 catch() 方法

promise.catch(onRejected);Code language: CSS (css)

在内部,catch() 方法调用 then(undefined, onRejected) 方法。

以下示例将 success 标志更改为 false 以模拟错误场景

let success = false;

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (success) {
        resolve([
          { username: 'john', email: '[email protected]' },
          { username: 'jane', email: '[email protected]' },
        ]);
      } else {
        reject('Failed to the user list');
      }
    }, 1000);
  });
}

const promise = getUsers();

promise.catch((error) => {
  console.log(error);
});Code language: JavaScript (javascript)

3) finally() 方法

有时,您希望无论 Promise 已完成还是拒绝都执行相同的代码段。例如


const render = () => {
  //...
};

getUsers()
  .then((users) => {
    console.log(users);
    render();
  })
  .catch((error) => {
    console.log(error);
    render();
  });Code language: JavaScript (javascript)

如您所见,render() 函数调用在 then()catch() 方法中重复出现。

要消除此重复并执行 render()(无论 Promise 已完成还是拒绝),可以使用 finally() 方法,如下所示


const render = () => {
  //...
};

getUsers()
  .then((users) => {
    console.log(users);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    render();
  });
Code language: JavaScript (javascript)

一个实用的 JavaScript Promise 示例

以下示例演示如何从服务器加载 JSON 文件并在网页上显示其内容。

假设您有以下 JSON 文件

https://tutorial.javascript.ac.cn/sample/promise/api.jsonCode language: JavaScript (javascript)

其内容如下

{
    "message": "JavaScript Promise Demo"
}Code language: JSON / JSON with Comments (json)

以下是包含一个按钮的 HTML 页面。单击按钮时,页面将从 JSON 文件加载数据并显示消息

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>JavaScript Promise Demo</title>
    <link href="css/style.css" rel="stylesheet">
</head>
<body>
    <div id="container">
        <div id="message"></div>
        <button id="btnGet">Get Message</button>
    </div>
    <script src="js/promise-demo.js">
    </script>
</body>
</html>Code language: HTML, XML (xml)

以下是 promise-demo.js 文件

function load(url) {
  return new Promise(function (resolve, reject) {
    const request = new XMLHttpRequest();
    request.onreadystatechange = function () {
      if (this.readyState === 4 && this.status == 200) {
        resolve(this.response);
      } else {
        reject(this.status);
      }
    };
    request.open('GET', url, true);
    request.send();
  });
}

const url = 'https://tutorial.javascript.ac.cn/sample/promise/api.json';
const btn = document.querySelector('#btnGet');
const msg = document.querySelector('#message');

btn.addEventListener('click', () => {
  load(URL)
    .then((response) => {
      const result = JSON.parse(response);
      msg.innerHTML = result.message;
    })
    .catch((error) => {
      msg.innerHTML = `Error getting the message, HTTP status: ${error}`;
    });
});
Code language: JavaScript (javascript)

工作原理。

首先,定义 load() 函数,该函数使用 XMLHttpRequest 对象从服务器加载 JSON 文件

function load(url) {
  return new Promise(function (resolve, reject) {
    const request = new XMLHttpRequest();
    request.onreadystatechange = function () {
      if (this.readyState === 4 && this.status == 200) {
        resolve(this.response);
      } else {
        reject(this.status);
      }
    };
    request.open('GET', url, true);
    request.send();
  });
}Code language: JavaScript (javascript)

在执行器中,如果 HTTP 状态代码为 200,我们将使用响应调用 resolve() 函数。否则,我们将使用 HTTP 状态代码调用 reject() 函数。

其次,注册按钮单击事件侦听器并调用 Promise 对象的 then() 方法。如果加载成功,我们将显示从服务器返回的消息。否则,我们将使用 HTTP 状态代码显示错误消息。


const url = 'https://tutorial.javascript.ac.cn/sample/promise/api.json';
const btn = document.querySelector('#btnGet');
const msg = document.querySelector('#message');

btn.addEventListener('click', () => {
  load(URL)
    .then((response) => {
      const result = JSON.parse(response);
      msg.innerHTML = result.message;
    })
    .catch((error) => {
      msg.innerHTML = `Error getting the message, HTTP status: ${error}`;
    });
});
Code language: JavaScript (javascript)

摘要

  • Promise 是一个封装异步操作结果的对象。
  • Promise 从等待中状态开始,最终结束于已完成状态或拒绝状态。
  • 使用 then() 方法来安排 Promise 已完成时执行的回调,以及使用 catch() 方法来安排 Promise 已拒绝时调用的回调。
  • 将您希望在 Promise 已完成或拒绝时执行的代码放在 finally() 方法中。

测验

本教程对您有帮助吗?