概述
异步调用,例如通过fetch、axios、AJAX 的HTTP get 请求或通过Promises 的其他异步调用都表现相似,因为函数到达其最后一行时返回的内容是实际上并不是你想要的结果。
问题说明
这是因为异步调用在执行下一行之前不会等待调用完成:
请注意,在下面的所有示例中,我将使用fetch,但该概念可以轻松扩展到其他调用类型。
getDataWithFetch();
function getDataWithFetch() {
console.log('start of outer function');
const url = 'https://jsonplaceholder.typicode.com/todos/1';
fetch(url).then(response => {
console.log('start of async function');
response.json();
console.log('end of async function');
});
console.log('end of outer function');
}
您可以通过运行代码 sn-p 看到,outer function 在async function 开始之前开始和结束。什么是 undefined 从 getDataWithFetch() 返回,因为它正在到达函数的末尾,并且没有 return 语句。如果我们更改代码以添加 return 语句,我们可以看到函数调用的哪一部分实际被返回:
const response = getDataWithFetch();
console.log(response);
function getDataWithFetch() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
fetch(url).then(response => {
return response.json();
});
return 'hello world';
}
现在该函数不再返回undefined,但它不再返回fetch 请求响应;相反,它返回'hello world'。这是因为fetch 请求没有阻止getDataWithFetch 函数的其余部分执行。要了解原因,我们需要了解 fetch 在幕后所做的事情。
承诺
解决
为此,我们需要了解 Javascript Promises。这里有一本非常适合初学者的读物:https://javascript.info/promise-basics,但简而言之:
Promise 是一个对象,当它完成异步工作时,它会调用“resolve”函数。其中“resolve”通过resolve(dataToReturn)返回预期数据。
所以在幕后,fetch 返回一个Promise,它会在检索到的数据被检索到时调用resolve,如下所示:
// just an example of what fetch somewhat looks like behind the scenes
function fetch(url) {
return new Promise(resolve => {
// waiting for response to come back from server
// maybe doing a bit of additional work
resolve(responseDataFromServer);
});
}
有了这个理解,我们现在知道我们需要返回对fetch的调用才能真正返回Promise,如下所示:
const response = getDataWithFetch();
console.log(response);
function getDataWithFetch() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
return fetch(url).then(response => response.json());
}
console.log 不再返回 undefined,但它也没有返回正确的响应。这是因为getDataWithFetch 现在从fetch 返回Promise 对象。是时候得到resolve的回复了。
.那么
要获取传递给resolve函数的数据,我们需要对返回的Promise使用then方法。
Promise 提供方法 then 来检索传递给 resolve 的值,如下所示:myReturnedPromise.then(dataToReturn => { /* do something with the data */ });。 then 只会在 dataToReturn 准备好被处理时执行。
最终解决方案
因此通过使用then 方法,我们可以将实际响应打印到控制台:
getDataWithFetch().then(response => {
console.log(response);
});
function getDataWithFetch() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
return fetch(url).then(response => response.json());
}
你有它!
过于冗长的解决方案
如果使该解决方案发挥作用的原因太多,您还可以通过显式返回 Promise 来查看它的细分:
getDataWithFetch().then(response => {
console.log(response);
});
function getDataWithFetch() {
return new Promise(resolve => {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
return fetch(url).then(response => {
resolve(response.json()));
// Note that the `resolve` does not have to be part of the `return`
}
});
}
异步等待解决方案
最近对 Javascript 的改进为我们提供了一种更简洁的方式来处理 Promises,使它们看起来是同步的而不是异步的。请注意,以下代码等效于上述解决方案,但更易于阅读:
async function getDataWithFetch() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url);
return response.json();
}
getDataWithFetch().then(response => {
console.log(response);
});
async 和await 是明确处理Promises 的干净而强大的替代方案,尤其是在涉及Promise Chaining 时。我建议在这里阅读更多关于它们的信息:https://javascript.info/async-await。