12

我遇到了一个我不完全理解的问题。我觉得可能有一些我没有掌握的概念,可以优化的代码,以及可能为了好的措施而抛出的错误。

为了大大简化整个流程:

  1. 向外部 API 发出请求
  2. 返回的 JSON 对象被解析并扫描链接引用
  3. 如果找到任何链接引用,则会发出其他请求以使用真实 JSON 数据填充/替换链接引用
  4. 替换所有链接引用后,将返回原始请求并用于构建内容

这是原始请求(#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,awaitPromise's 我相信他们的预期庄园。最终发生的情况:对引用数据的调用(获取 _scan 的结果)最终发生在原始请求返回之后。这最终会为内容模板提供不完整的数据。

有关我的构建设置的其他信息:

  • npm@2.14.2
  • 节点@4.0.0
  • webpack@1.12.2
  • babel@5.8.34
  • babel-loader@5.4.0
4

1 回答 1

19

我相信问题出在您的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完成,promise 就会被解析并_scan返回,即使其他调用 viaforEach的异步函数仍在执行。

您需要将 更改forEach为 amap以返回一组承诺,然后您可以await*从中_scan执行(如果您想同时执行它们,然后在它们全部完成时调用某些东西),或者一个接一个地执行它们-time 如果您希望它们按顺序执行。


作为旁注,如果我没看错的话,你的一些异步函数可以简化一点;请记住,虽然awaitingasync函数调用返回一个值,但简单地调用它会返回另一个 Promise,并且从函数返回一个值与async返回一个在非函数中解析为该值的 Promise 相同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;
  }
}
于 2015-12-19T07:03:30.983 回答