myfreax

JavaScript Promises

您将了解 JavaScript promises 以及如何有效地使用 promises

JavaScript Promises
JavaScript Promises

在本教程中,您将了解 JavaScript promises 以及如何有效地使用 promises。

JavaScript promises

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

function getUsers() {
  return [
    { username: 'john', email: 'john@test.com' },
    { username: 'jane', email: 'jane@test.com' },
  ];
}

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

要从 getUsers() 函数返回的用户列表按用户名查找用户,可以使用 findUser() 函数:

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

findUser() 函数:

  • 首先,通过调用 getUsers() 函数获取用户数组
  • 然后,通过 Array 对象的 find() 方法找到指定 username 的用户。
  • 最后,返回匹配的用户。

下面展示如何查找 username 是 'john' 的用户:

function getUsers() {
  return [
    { username: 'john', email: 'john@test.com' },
    { username: 'jane', email: 'jane@test.com' },
  ];
}

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

console.log(findUser('john'));

输出:

{ username: 'john', email: 'john@test.com' }

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

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

要模拟延迟,可以使用 setTimeout() 函数。例如:

function getUsers() {
  let users = [];

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

  return users;
}

代码是如何运行的。

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

现在 getUsers() 将无法正常工作并始终返回一个空数组。因此,findUser() 函数不会按预期工作:

function getUsers() {
  let users = [];
  setTimeout(() => {
    users = [
      { username: 'john', email: 'john@test.com' },
      { username: 'jane', email: 'jane@test.com' },
    ];
  }, 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'));

输出:

undefined

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

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

使用回调处理异步操作

以下示例将回调参数添加到 getUsers()findUser() 函数:

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

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

findUser('john', console.log);

输出:

{ username: 'john', email: 'john@test.com' }

在此示例中,getUsers() 函数接受一个回调函数作为参数,并在 setTimeout() 函数内指定 users 数组作为回调函数参数并调用。此外,findUser() 函数接受用于处理匹配用户的回调函数。

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

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

理解 JavaScript Promise

根据定义,promise 是一个封装异步操作结果的对象

promise 对象的状态可以是以下之一:

  • Pending (等待)
  • Fulfilled with a value (值已经填充)
  • Rejected for a reason (拒绝与指定的原因)

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

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

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

创建 Promise

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

const promise = new Promise((resolve, reject) => {
  // 做一些阻塞的操作
  // ...

  // return the state
  if (success) {
    resolve(value);
  } else {
    reject(error);
  }
});

promise 构造函数接受执行异步操作的回调函数。这个函数通常被称为执行器。反过来,执行器接受称为 resolvereject 的两个回调函数。

请注意,传递给执行执行器的回调函数 resolvereject 只是约定俗成的命名。

如果异步操作成功完成,执行器将调用 resolve() 函数以将 promise 的状态从待定状态更改为已完成。

如果出现错误,执行器将调用函数 reject() 将 promise 的状态从挂起更改为拒绝,并给出错误原因。

一旦 promise 达到 fulfilled 或 rejected 状态,它就会停留在状态并且不能进入另一个状态。

换句话说,一个 promise 不能从一个 fulfilled 状态到 rejected 状态,反之亦然。此外,它不能从 fulfilledrejected 状态返回到 pending 状态。

一旦创建了一个 Promise 对象,它的状态就是挂起的。如果一个 promise 到达fulfilledrejected 状态,它就被 Resolved(解决)。

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

Promise:then, catch, finally

then() 方法

要在 Promise 完成时获取 Promise 的值,您可以调用 Promise 对象的 then() 方法。下面展示 then() 方法的语法:

promise.then(onFulfilled,onRejected);

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

如果 Promise 完成,then() 方法会调用 onFulfilled() 与一个值,如果 Promise 被拒绝,onRejected() 方法会被调用与一个错误原因。

请注意,then() 方法的参数 onFulfilledonRejected 参数都是可选的。

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

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

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

const promise = getUsers();
promise.then(onFulfilled);

输出:

[
  { username: 'john', email: 'john@test.com' },
  { username: 'jane', email: 'jane@test.com' }
]

在这个例子中:

  • 首先,定义Promise 完成时要调用的  onFulfilled() 函数。
  • 其次,调用 getUsers() 函数返回一个 promise 对象。
  • 第三,调用 promise 对象的 then() 方法,将用户列表输出到控制台。

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

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

const promise = getUsers();

promise.then((users) => {
  console.log(users);
});

因为 getUsers() 函数返回一个 promise 对象,所以您可以使用如下方法链式调用  then() 方法:

// getUsers() function
//...

getUsers().then((users) => {
  console.log(users);
});

在此示例中,getUsers() 函数总是成功。为了模拟错误,我们可以使用 success 变量模拟错误:

let success = true;

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (success) {
        resolve([
          { username: 'john', email: 'john@test.com' },
          { username: 'jane', email: 'jane@test.com' },
        ]);
      } 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);

代码如何运行的。

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

如果成功,则 getUsers() 函数的 Promise 将返回用户列表。否则,它会被拒绝并显示一条错误消息。

其次,定义 onFulfilledonRejected 函数。

最后,调用 getUsers() 函数返回 Promise 并使用 onFulfilledonRejected函数调用 then() 方法。

下面展示如何使用箭头函数作为 then() 方法的参数:

// getUsers() function
// ...

const promise = getUsers();
promise.then(
  (users) => console.log,
  (error) => console.log
);

catch() 方法

如果你想在 promise 的状态被拒绝时得到错误,你可以使用 Promise 对象 catch() 的方法:

promise.catch(onRejected);

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

以下示例 success 变量更改为 false 模拟错误场景:

let success = false;

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

const promise = getUsers();

promise.catch((error) => {
  console.log(error);
});

finally() 方法

有时,无论 promise 是完成还是拒绝,您都希望执行同一段代码。例如:


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

getUsers()
  .then((users) => {
    console.log(users);
    render();
  })
  .catch((error) => {
    console.log(error);
    render();
  });

如您所见,render() 函数调用是重复的,在 then()catch() 方法。要删除此重复,无论 Promise 是完成还是拒绝都执行 render() 函数,您可以使用 Promise 对象的 finally() 方法。


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

getUsers()
  .then((users) => {
    console.log(users);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    render();
  });

结论

  • Promise 是封装异步操作结果的对象。
  • promise 以 pending 状态开始,以 fulfilled 状态或 rejected 状态结束。
  • 使用 then() 方法来安排在 Promise 完成时执行的回调,并使用 catch() 方法来安排在 Promise 被拒绝时调用的回调。
  • 无论 Promise 是完成还是拒绝都要执行指定的代码,可以将代码放在 finally() 方法。

内容导航