Async/ Ожидание не ждет
Я столкнулся с проблемой, которую я не до конца понимаю. Я чувствую, что есть вероятные концепции, которые я не понял, код, который можно оптимизировать, и, возможно, ошибка, добавленная для хорошей меры.
Чтобы значительно упростить общий поток:
- Запрос сделан к внешнему API
- Возвращенный объект JSON анализируется и проверяется на наличие ссылок.
- Если найдены какие-либо ссылки, дополнительные запросы делаются для заполнения / замены ссылок на реальные данные JSON.
- Как только все ссылки ссылки были заменены, исходный запрос возвращается и используется для создания контента
Вот оригинальный запрос (# 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) в конечном итоге происходят после возврата исходного запроса. Это приводит к предоставлению неполных данных в шаблон контента.
Дополнительная информация о настройке моей сборки:
- 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
на самом деле в подфункции. Кроме того, это будет выполнять каждое обещание одновременно, а это не то, что мы намеревались.Урок: будьте осторожны, когда у вас есть какая-либо функция внутри вашей асинхронной функции.
await
будет только приостанавливать свою родительскую функцию, поэтому убедитесь, что она делает то, что вы на самом деле думаете.
Так что каждая итерация forEach
вызов выполняется одновременно; они не исполняются по одному. Как только тот, который соответствует критериям i === keys.length - 1
заканчивается, обещание выполнено и _scan
возвращает, даже если другие асинхронные функции вызваны через forEach
все еще выполняется.
Вам нужно будет либо изменить forEach
к map
вернуть массив обещаний, которые вы можете затем 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;
}
}