Async / Await не ждет

7

У меня проблема, которую я не совсем понимаю. Я чувствую, что есть вероятные концепции, которые я не понял, код, который можно было бы оптимизировать, и, возможно, ошибку, заброшенную для хорошей оценки.

Чтобы значительно упростить общий поток:

  • Запрос на внешний 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) заканчиваются после возвращения исходного запроса. Это приводит к предоставлению неполных данных шаблону контента.

Дополнительная информация о моей установке сборки:

  • 0
    Почему вы смешиваете обещания и async / await: return new Promise(async (resolve, reject) => { ... } ? async _get(type, id) { ... } это не должно быть async _get(type, id) { ... } и вообще никаких обещаний) ?
Теги:
webpack
babeljs
ecmascript-next

1 ответ

11
Лучший ответ

Я считаю, что проблема заключается в вызове 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;
  }
}
  • 0
    Это было очень полезно. Спасибо. Только одна небольшая заметка, вы случайно вырезали var val = data.fields[key];
  • 0
    @ArrayKnight Рад, что это было полезно! Исправлена опечатка, спасибо ^ _ ^

Ещё вопросы

Сообщество Overcoder
Наверх
Меню