У меня проблема, которую я не совсем понимаю. Я чувствую, что есть вероятные концепции, которые я не понял, код, который можно было бы оптимизировать, и, возможно, ошибку, заброшенную для хорошей оценки.
Чтобы значительно упростить общий поток:
Здесь исходный запрос (# 1):
await Store.get(Constants.Contentful.ENTRY, Contentful[page.file])
Store.get представлен:
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);
}
}
});
});
}
Когда возвращается запись, она сканируется:
_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 вместо ссылки:
_inject(fields, key, index, data) {
if(isNaN(index)) {
fields[key] = data;
} else {
fields[key][index] = data;
}
}
Заметьте, я использую async
, await
и Promise
Я верю в их задуманную усадьбу. Что происходит:. Вызовы для ссылочных данных (получаемых в результате _scan) заканчиваются после возвращения исходного запроса. Это приводит к предоставлению неполных данных шаблону контента.
Дополнительная информация о моей установке сборки:
Я считаю, что проблема заключается в вызове forEach
в _scan
. Для справки см. Этот отрывок в Укрощение асинхронного зверя с ES7:
Однако, если вы попытаетесь использовать функцию async, вы получите более тонкую ошибку:
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');
Это будет скомпилировано, но проблема в том, что это будет распечатываться:
main loop done 0 1 2
Что происходит, так это то, что основная функция выходит рано, потому что
await
фактически находится в подфункции. Кроме того, это будет выполнять каждое обещание одновременно, что не так, как мы предполагали.Урок: будьте осторожны, если у вас есть какая-либо функция внутри вашей асинхронной функции.
await
будет только приостанавливать свою родительскую функцию, поэтому убедитесь, что она делает то, что вы на самом деле думаете.
Таким образом, каждая итерация вызова forEach
выполняется одновременно; они не выполняют одно за раз. Как только заканчивается тот, который соответствует критериям i === keys.length - 1
, обещание разрешается и возвращается _scan
, хотя другие функции асинхронизации, вызванные через forEach
, все еще выполняются.
Вам нужно либо изменить forEach
на map
, чтобы вернуть массив promises, который вы можете затем await*
из _scan
(если вы хотите выполнить их все одновременно, а затем вызвать что-то, когда все это делается) или выполнить их один раз в то время, если вы хотите, чтобы они выполнялись последовательно.
В качестве побочного примечания, если я правильно их читаю, некоторые из ваших асинхронных функций могут быть немного упрощены; помните, что в то время как await
вызов функции async
возвращает значение, просто его вызов возвращает другое обещание, а возврат значения из функции async
совпадает с возвратом обещания, которое разрешает это значение в не- - async
. Так, например, _get
может быть:
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
выполнялись одновременно):
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;
}
}
var val = data.fields[key];
return new Promise(async (resolve, reject) => { ... }
?async _get(type, id) { ... }
это не должно бытьasync _get(type, id) { ... }
и вообще никаких обещаний) ?