Async / Await не ждет
я столкнулся с проблемой, которую я не полностью понимаю. Я чувствую, что есть вероятные концепции, которые я не понял, код, который можно оптимизировать, и, возможно, ошибка, добавленная для хорошей меры.
значительно упростить общую подачу:
- запрос сделан к внешнему API
- возвращенный объект JSON анализируется и сканируется для ссылок
- если ссылки не найдены, дополнительных запросов для заполнения / замены ссылок на ссылки реальными данными JSON
- после того, как все ссылки ссылки были заменены, исходный запрос возвращается и используется для создания контента
вот оригинальный запрос (#1):
await Store.get(Constants.Contentful.ENTRY, Contentful[page.file])
магазине.вам представлен:
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) в конечном итоге происходят после возврата исходного запроса. Это приводит к предоставлению неполных данных содержимому шаблон.
дополнительная информация о моей настройке сборки:
- npm@2.14.2
- node@4.0.0
- webpack@1.12.2
- babel@5.8.34
- babel-loader@5.4.0
1 ответов
я считаю, что проблема в вашем forEach
вызов в _scan
. Для справки, см. Этот отрывок в Укрощение асинхронного зверя с помощью 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');
это будет компилироваться, но проблема в том, что это будет напечатано:
main loop done 0 1 2
происходит то, что основная функция выходит рано, потому что
await
is фактически в подфункции. Кроме того, это будет выполнять каждое обещание по совместительству, что не то, что мы намеревались.урок: будьте осторожны, когда у вас есть функция внутри функции async. The
await
только приостановит свою родительскую функцию, поэтому проверьте, что она делает то, что вы на самом деле думаете, что она делает.
Итак, каждая итерация forEach
вызов выполняется одновременно; они не выполняются по одному за раз. Как только тот, который соответствует критериям i === keys.length - 1
заканчивается, обещание разрешено и _scan
возвращает, хотя другие асинхронные функции вызываются через forEach
все еще выполняются.
вам нужно будет либо изменить forEach
до map
чтобы вернуть массив обещаний, которые вы можете затем await*
С _scan
(если вы хотите выполнить их все одновременно, а затем вызвать что-то, когда все они будут сделаны), или выполнить их по одному, если вы хотите, чтобы они выполнялись в последовательность.
в качестве примечания, если я читаю их правильно, некоторые из ваших асинхронных функций могут быть немного упрощены; помните, что, в то время как await
ing an async
вызов функции возвращает значение, просто вызывая его возвращает другое обещание и возвращает значение из async
функция такая же, как возврат обещания, которое разрешает это значение в не-