asynchronous/等待不等待
我遇到了一个我不完全明白的问题。 我觉得有可能是我没有掌握的概念,可以优化的代码,可能还有一个错误的方法。
要大大简化整体stream程:
- 向外部API发出请求
- 对返回的JSON对象进行分析并扫描链接引用
- 如果find任何链接引用,则会使用真正的JSON数据填充/replace链接引用
- 一旦所有链接引用被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; } }
请注意,我正在使用async
, await
和Promise
,我相信他们的预定庄园。 发生什么事情:返回原始请求后,引用数据的调用(由_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; } }