asynchronous/等待不等待

我遇到了一个我不完全明白的问题。 我觉得有可能是我没有掌握的概念,可以优化的代码,可能还有一个错误的方法。

要大大简化整体stream程:

  1. 向外部API发出请求
  2. 对返回的JSON对象进行分析并扫描链接引用
  3. 如果find任何链接引用,则会使用真正的JSON数据填充/replace链接引用
  4. 一旦所有链接引用被replace,原始请求将被返回并用于构build内容

这里是原始请求(#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); } }); } 

如果find链接引用,则会发出其他请求,然后将生成的JSON注入到原始JSON中以代替引用:

 _inject(fields, key, index, data) { if(isNaN(index)) { fields[key] = data; } else { fields[key][index] = data; } } 

请注意,我正在使用asyncawaitPromise ,我相信他们的预定庄园。 发生什么事情:返回原始请求后,引用数据的调用(由_scan产生)最终会发生。 这最终将提供不完整的数据给内容模板。

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

  • npm@2.14.2
  • node@4.0.0
  • webpack@1.12.2
  • babel@5.8.34
  • babel-loader@5.4.0

我相信这个问题是在_scan中的forEach调用。 作为参考,请参阅使用ES7调用asynchronous野兽的这一段:

但是,如果你尝试使用asynchronous函数,那么你会得到一个更微妙的错误:

 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 

发生的事情是,主要function是提前退出,因为await实际上是在子function中。 此外,这将同时执行每个承诺,这不是我们所期望的。

教训是:当你的asynchronous函数中有任何函数时要小心。 await只会暂停它的父function,所以请检查它是否正在做你认为正在做的事情。

所以forEach调用的每个迭代都并发地运行; 他们没有一次执行一个。 只要符合条件i === keys.length - 1完成,即使通过forEach调用的其他asynchronous函数仍在执行,promise也会被parsing并返回_scan

您需要将forEach更改为map以返回一个promise数组,然后您可以await* _scan (如果要同时执行它们,然后在完成时调用它们),或者执行它们如果您希望它们按顺序执行,则一次一个。


作为一个侧面说明,如果我正确地阅读它们,一些asynchronous函数可以简化一下; 请记住,在await async函数调用返回一个值时,只需调用它就会返回另一个promise,并且从async函数返回一个值与返回一个在非async函数中parsing该值的promise相同。 所以,例如, _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; } }