问题描述
我遇到了一个我不太了解的问题.我觉得可能有一些我尚未掌握的概念,可能被优化的代码以及可能因适当原因而引发的错误.
I'm running into an issue which I don't fully understand. I feel like there are likely concepts which I haven't grasped, code that could be optimized, and possibly a bug thrown in for good measure.
要大大简化总体流程,请执行以下操作:
To greatly simplify the overall flow:
- 对外部API的请求
- 解析返回的JSON对象并对其进行扫描以查找链接引用
- 如果找到任何链接引用,则会发出其他请求以使用真实的JSON数据填充/替换链接引用
- 一旦所有链接引用都被替换,原始请求将返回并用于构建内容
这是原始请求(#1):
Here, is the original request (#1):
await Store.get(Constants.Contentful.ENTRY, Contentful[page.file])
Store.get表示为:
Store.get is represented by:
async get(type, id) {
return await this._get(type, id);
}
哪个电话:
_get(type, id) {
return new Promise(async (resolve, reject) => {
var data = _json[id] = _json[id] || await this._api(type, id);
console.log(data)
if(isAsset(data)) {
resolve(data);
} else if(isEntry(data)) {
await this._scan(data);
resolve(data);
} else {
const error = 'Response is not entry/asset.';
console.log(error);
reject(error);
}
});
}
API调用为:
_api(type, id) {
return new Promise((resolve, reject) => {
Request('http://cdn.contentful.com/spaces/' + Constants.Contentful.SPACE + '/' + (!type || type === Constants.Contentful.ENTRY ? 'entries' : 'assets') + '/' + id + '?access_token=' + Constants.Contentful.PRODUCTION_TOKEN, (error, response, data) => {
if(error) {
console.log(error);
reject(error);
} else {
data = JSON.parse(data);
if(data.sys.type === Constants.Contentful.ERROR) {
console.log(data);
reject(data);
} else {
resolve(data);
}
}
});
});
}
返回条目时,将对其进行扫描:
When an entry is returned, it is scanned:
_scan(data) {
return new Promise((resolve, reject) => {
if(data && data.fields) {
const keys = Object.keys(data.fields);
keys.forEach(async (key, i) => {
var val = data.fields[key];
if(isLink(val)) {
var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id);
this._inject(data.fields, key, undefined, child);
} else if(isLinkArray(val)) {
var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id));
children.forEach((child, index) => {
this._inject(data.fields, key, index, child);
});
} else {
await new Promise((resolve) => setTimeout(resolve, 0));
}
if(i === keys.length - 1) {
resolve();
}
});
} else {
const error = 'Required data is unavailable.';
console.log(error);
reject(error);
}
});
}
如果找到了链接引用,则会发出其他请求,然后将结果JSON替换为原始JSON来代替引用:
If link references are found, additional requests are made and then the resulting JSON is injected into the original JSON in place of the reference:
_inject(fields, key, index, data) {
if(isNaN(index)) {
fields[key] = data;
} else {
fields[key][index] = data;
}
}
注意,我使用的是async
,await
和Promise
,我相信它们的预定庄园. 最终发生的事情:在返回原始请求后,最终发生了对引用数据的调用(由_scan生成的结果).最终会向内容模板提供不完整的数据.
Notice, I'm using async
, await
, and Promise
's I believe in their intended manor. What ends up happening: The calls for referenced data (gets resulting of _scan) end up occurring after the original request is returned. This ends up providing incomplete data to the content template.
有关我的构建设置的其他信息:
Additional information concerning my build setup:
- npm@2.14.2
- node@4.0.0
- webpack@1.12.2
- babel@5.8.34
- babel-loader@5.4.0
推荐答案
我相信问题出在您在_scan
中的forEach
调用中.供参考,请参见将异步野兽命名使用ES7 :
I believe the issue is in your forEach
call in _scan
. For reference, see this passage in Taming the asynchronous beast with ES7:
let docs = [{}, {}, {}];
// WARNING: this won't work
docs.forEach(async function (doc, i) {
await db.post(doc);
console.log(i);
});
console.log('main loop done');
这将编译,但问题是它将打印出来:
This will compile, but the problem is that this will print out:
main loop done
0
1
2
正在发生的事情是,由于await
实际上位于子功能中,因此主要功能正在提早退出.此外,这将同时执行每个承诺,这不是我们想要的.
What's happening is that the main function is exiting early, because the await
is actually in the sub-function. Furthermore, this will execute each promise concurrently, which is not what we intended.
课程是:异步函数中有任何函数时,请当心. await
只会暂停其父函数,因此请检查它是否正在执行您实际认为正在执行的操作.
The lesson is: be careful when you have any function inside your async function. The await
will only pause its parent function, so check that it's doing what you actually think it's doing.
因此,forEach
调用的每个迭代都同时运行;他们一次不执行一个.符合条件i === keys.length - 1
的条件一经完成,即使仍通过forEach
调用的其他异步函数仍在执行,承诺也会被解决并_scan
返回.
So each iteration of the forEach
call is running concurrently; they're not executing one at a time. As soon as the one that matches the criteria i === keys.length - 1
finishes, the promise is resolved and _scan
returns, even though other async functions called via forEach
are still executing.
您需要将forEach
更改为map
以返回承诺数组,然后可以从_scan
中await*
(如果要同时执行所有承诺,然后在调用时调用某些内容)它们都已完成),或者如果您希望它们依次执行,则一次执行一次.
You would need to either change the forEach
to a map
to return an array of promises, which you can then await*
from _scan
(if you want to execute them all concurrently and then call something when they're all done), or execute them one-at-a-time if you want them to execute in sequence.
请注意,如果我没看错的话,您的某些异步功能可以简化一些;请记住,虽然await
调用async
函数会返回一个值,但简单地调用它会返回另一个诺言,而从async
函数返回一个值与返回一个在非运算符中解析为该值的诺言相同-async
功能.因此,例如,_get
可以是:
As a side note, if I'm reading them right, some of your async functions can be simplified a bit; remember that, while await
ing an async
function call returns a value, simply calling it returns another promise, and returning a value from an async
function is the same as returning a promise that resolves to that value in a non-async
function. So, for example, _get
can be:
async _get(type, id) {
var data = _json[id] = _json[id] || await this._api(type, id);
console.log(data)
if (isAsset(data)) {
return data;
} else if (isEntry(data)) {
await this._scan(data);
return data;
} else {
const error = 'Response is not entry/asset.';
console.log(error);
throw error;
}
}
类似地,_scan
可能是(假设您希望forEach
主体同时执行):
Similarly, _scan
could be (assuming you want the forEach
bodies to execute concurrently):
async _scan(data) {
if (data && data.fields) {
const keys = Object.keys(data.fields);
const promises = keys.map(async (key, i) => {
var val = data.fields[key];
if (isLink(val)) {
var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id);
this._inject(data.fields, key, undefined, child);
} else if (isLinkArray(val)) {
var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id));
children.forEach((child, index) => {
this._inject(data.fields, key, index, child);
});
} else {
await new Promise((resolve) => setTimeout(resolve, 0));
}
});
await* promises;
} else {
const error = 'Required data is unavailable.';
console.log(error);
throw error;
}
}
这篇关于异步/等待不等待的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!