异步/等待不等待

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;
    

注意,我使用的是asyncawaitPromise,我相信他们的预期庄园。 最终会发生什么: 对引用数据的调用(获取 _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

以上是关于异步/等待不等待的主要内容,如果未能解决你的问题,请参考以下文章

异步函数 - 等待不等待承诺

异步/等待不等待返回值

在映射下一项之前,异步等待映射不等待异步函数在映射函数内部完成

Javascript异步等待不等待猫鼬等待

异步/等待不等待

异步等待不等待 useEffect 内的响应