学习JavaScript中的回调函数、Promises和async-await

48 阅读4分钟

简介

JavaScript把异步编程作为一个特点来宣传。这意味着,如果任何动作需要一段时间,你的程序可以在动作完成的同时继续做其他事情。一旦这个动作完成,你就可以用这个结果做一些事情。这对于获取数据等功能来说是一个很好的特性,但对于新手来说,可能会感到困惑。在JavaScript中,我们有几种不同的方法来处理异步性:回调函数、承诺和async-await。

回调函数

回调函数是你提供的一个函数,它将在异步操作完成后被执行。让我们创建一个假的用户数据获取器,并使用回调函数对结果做一些处理。

假的数据获取器

首先,我们创建一个不需要回调函数的假数据提取器。由于fakeData 在300毫秒内不存在,我们不能同步访问它。

const fetchData = (userId) => {
  setTimeout(() => {
    const fakeData = {
      id: userId,
      name: 'George',
    };
    // Our data fetch resolves
    // After 300ms. Now what?
  }, 300);
};

为了能够真正对我们的fakeData ,我们可以向fetchData 传递一个函数的引用,这个函数将处理我们的数据!

const fetchData = (userId, callback) => {
  setTimeout(() => {
    const fakeData = {
      id: userId,
      name: 'George',
    };
    callback(fakeData);
  }, 300);
};

让我们创建一个基本的回调函数,并对其进行测试。

const cb = (data) => {
  console.log("Here's your data:", data);
};

fetchData(5, cb);

300ms后,我们应该看到下面的记录。

Here's your data: {id: 5, name: "George"}

承诺

Promise对象代表了JavaScript中一个操作的最终完成。Promise可以是resolve ,也可以是reject 。当一个Promise解析时,你可以用then then方法处理其返回值。如果一个Promise被拒绝,你可以使用catch the error来处理它。

Promise对象的语法如下。

new Promise(fn);

fn 是一个函数,它接收一个resolve 函数,并且可以选择接收一个reject 函数。

fn = (resolve, reject) => {};

假数据获取器(含承诺)

让我们使用与之前相同的假数据提取器。我们将返回一个新的Promise 对象,而不是传递一个回调,该对象将在300毫秒后与我们用户的数据一起解析。作为奖励,我们也可以给它一个拒绝的小机会。

const fetchData = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() < 0.1) {
        reject('Fetch failed!');
      }
      const fakeData = {
        id: userId,
        name: 'George',
      };
      resolve(fakeData);
    }, 300);
  });
};

我们新的fetchData 函数可以如下使用。

fetchData(5)
  .then((user) => {
    console.log("Here's your data:", user);
  })
  .catch((err) => {
    console.error(err);
  });

如果fetchData 成功解析(这将在90%的时间内发生),我们将记录我们的用户数据,就像我们在回调方案中所做的那样。如果它被拒绝,那么我们将console.error 我们创建的错误信息("获取失败!")。

关于承诺的一个好处是你可以用链子来执行后续的承诺。例如,我们可以做这样的事情。

fetchData(5)
  .then((user) => {
    return someOtherPromise(user);
  })
  .then((data) => {
    console.log(data);
  })
  .catch((err) => {
    console.error(err);
  });

此外,我们可以将一个承诺数组传递给Promise.all ,以便在所有承诺被解决后才采取行动。

Promise.all([fetchData(5), fetchData(10)])
  .then((users) => {
    console.log("Here's your data:", users);
  })
  .catch((err) => {
    console.error(err);
  });

在这种情况下,如果两个承诺都被成功解决,下面的内容将被记录下来。

Here's your data:
[{ id: 5, name: "George" }, { id: 10, name: "George" }]

Async-Await

Async-await提供了一种不同的语法来编写Promise,有些人认为这种语法更清晰。使用async-await,你可以创建一个async 函数。在这个异步函数中,你可以在执行后续代码之前await 一个Promise的结果让我们看看我们的数据获取的例子。

const fetchUser = async (userId) => {
  const user = await fetchData(userId);
  console.log("Here's your data:", user);
};
fetchUser(5);

很好,对吗?一个小问题:我们没有处理我们的Promise拒绝情况。我们可以用try/catch 来做这个。

const fetchUser = async (userId) => {
  try {
    const user = await fetchData(userId);
    console.log("Here's your data:", user);
  } catch (err) {
    console.error(err);
  }
};
fetchUser(5);

浏览器/节点支持

由于回调函数只是被传递给其他函数的普通函数,所以不需要担心支持问题。Promises从ECMAScript 2015开始就是标准的,并且有不错的支持,但在Internet Explorer中不支持。Async-await比较新(从ECMAScript 2017开始成为标准),在较新的浏览器版本中有很好的支持。同样,它在Internet Exporer中不被支持。

  • MDN -[Promise 浏览器支持]
  • MDN -[异步函数浏览器支持]

在节点方面,自nove v7.6以来,async-await(以及因此的Promise)得到了良好的支持。