简介
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)得到了良好的支持。