3

JavaScript Promises | myfreax

 1 year ago
source link: https://www.myfreax.com/javascript-promises/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
JavaScript Promises

JavaScript Promises

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

JavaScript promises

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

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

每个用户对象都有两个属性 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: '[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'));
{ username: 'john', email: '[email protected]' }

findUser() 函数中的代码是同步并阻塞的。findUser() 函数执行 getUsers() 函数获取用户数组,调用数组 的 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;
}

代码是如何运行的。

  • 首先,定义一个数组 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'));
undefined

因为 getUsers() 返回一个空数组,所以 users 数组是空的(A 行)。在数组上调用 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);
{ username: 'john', email: '[email protected]' }

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

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

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

理解 JavaScript Promise

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

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

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

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

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

JavaScript-Promise-Fulfilled.svg

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

JavaScript-Promise-Rejected.svg

创建 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(解决)。

JavaScript-Promises.svg

请注意,您在实践中很少会创建 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: '[email protected]' },
        { username: 'jane', email: '[email protected]' },
      ]);
    }, 1000);
  });
}

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

const promise = getUsers();
promise.then(onFulfilled);
[
  { username: 'john', email: '[email protected]' },
  { username: 'jane', email: '[email protected]' }
]

在这个例子中:

  • 首先,定义Promise 完成时要调用的  onFulfilled() 函数。
  • 其次,调用 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);
});

因为 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: '[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);

代码如何运行的。

首先,定义 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: '[email protected]' },
          { username: 'jane', email: '[email protected]' },
        ]);
      } 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() 方法。

微信公众号

支付宝打赏

myfreax 淘宝打赏

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK