异步/等待不等待
Posted
技术标签:
【中文标题】异步/等待不等待【英文标题】:Async/Await not waiting 【发布时间】:2016-03-25 18:32:43 【问题描述】:我遇到了一个我不完全理解的问题。我觉得可能有一些我没有掌握的概念,可以优化的代码,以及可能会抛出一个很好的错误。
大大简化整体流程:
-
向外部 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【问题讨论】:
你为什么要混合 promise 和 async/await:return new Promise(async (resolve, reject) => ...
?不应该是async _get(type, id) ...
,根本就没有承诺吗?
【参考方案1】:
我认为问题出在您的forEach
呼叫_scan
。供参考,见Taming the asynchronous beast with 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
实际上在子函数中。此外,这将并发执行每个promise,这不是我们想要的。教训是:当你的异步函数中有任何函数时要小心。
await
只会暂停其父函数,因此请检查它是否正在执行您实际认为它正在执行的操作。
所以forEach
调用的每次迭代都是同时运行的;他们不是一次执行一个。只要符合条件 i === keys.length - 1
的函数完成,promise 就会被解析并返回 _scan
,即使通过 forEach
调用的其他异步函数仍在执行。
您需要将forEach
更改为map
以返回一组promise,然后您可以将await*
从_scan
更改为_scan
(如果您想同时执行它们,然后在它们都已完成),或者如果您希望它们按顺序执行,则一次执行它们。
附带说明,如果我没看错的话,您的一些异步函数可以稍微简化一点;请记住,虽然await
调用async
函数返回一个值,但简单地调用它会返回另一个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;
【讨论】:
这很有帮助。谢谢你。就一个小字条,不小心剪掉了var val = data.fields[key];
@ArrayKnight 很高兴它有帮助!修正错字,谢谢^_^
对我来说走出这个地狱最重要的部分是“教训是:当你的异步函数中有任何函数时要小心。等待只会暂停它的父函数,所以检查一下它正在做你实际上认为它正在做的事情。”谢谢! @MichelleTilley以上是关于异步/等待不等待的主要内容,如果未能解决你的问题,请参考以下文章