异步/等待不等待 [英] Async/Await not waiting

查看:83
本文介绍了异步/等待不等待的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了一个我不太了解的问题.我觉得可能有一些我尚未掌握的概念,可能被优化的代码以及可能因适当原因而引发的错误.

I'm running into an issue which I don't fully understand. I feel like there are likely concepts which I haven't grasped, code that could be optimized, and possibly a bug thrown in for good measure.

要大大简化总体流程,请执行以下操作:

To greatly simplify the overall flow:

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

这是原始请求(#1):

Here, is the original request (#1):

await Store.get(Constants.Contentful.ENTRY, Contentful[page.file])

Store.get表示为:

Store.get is represented by:

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);
                }
            }
        });
    });
}

返回条目时,将对其进行扫描:

When an entry is returned, it is scanned:

_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来代替引用:

If link references are found, additional requests are made and then the resulting JSON is injected into the original JSON in place of the reference:

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

注意,我使用的是asyncawaitPromise,我相信它们的预定庄园. 最终发生的事情:在返回原始请求后,最终发生了对引用数据的调用(由_scan生成的结果).最终会向内容模板提供不完整的数据.

Notice, I'm using async, await, and Promise's I believe in their intended manor. What ends up happening: The calls for referenced data (gets resulting of _scan) end up occurring after the original request is returned. This ends up providing incomplete data to the content template.

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

Additional information concerning my build setup:

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

推荐答案

我相信问题出在您在_scan中的forEach调用中.供参考,请参见将异步野兽命名使用ES7 :

I believe the issue is in your forEach call in _scan. For reference, see this passage in Taming the asynchronous beast with ES7:

但是,如果您尝试使用异步功能,则会遇到更细微的错误:

However, if you try to use an async function, then you will get a more subtle bug:

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');

这将编译,但问题是它将打印出来:

This will compile, but the problem is that this will print out:

main loop done
0
1
2

正在发生的事情是,由于await实际上位于子功能中,因此主要功能正在提早退出.此外,这将同时执行每个承诺,这不是我们想要的.

What's happening is that the main function is exiting early, because the await is actually in the sub-function. Furthermore, this will execute each promise concurrently, which is not what we intended.

课程是:异步函数中有任何函数时,请当心. await只会暂停其父函数,因此请检查它是否正在执行您实际认为正在执行的操作.

The lesson is: be careful when you have any function inside your async function. The await will only pause its parent function, so check that it's doing what you actually think it's doing.

因此,forEach调用的每个迭代都同时运行;他们一次不执行一个.符合条件i === keys.length - 1的条件一经完成,即使仍通过forEach调用的其他异步函数仍在执行,承诺也会被解决并_scan返回.

So each iteration of the forEach call is running concurrently; they're not executing one at a time. As soon as the one that matches the criteria i === keys.length - 1 finishes, the promise is resolved and _scan returns, even though other async functions called via forEach are still executing.

您需要将forEach更改为map以返回承诺数组,然后可以从_scanawait*(如果要同时执行所有承诺,然后在调用时调用某些内容)它们都已完成),或者如果您希望它们依次执行,则一次执行一次.

You would need to either change the forEach to a map to return an array of promises, which you can then await* from _scan (if you want to execute them all concurrently and then call something when they're all done), or execute them one-at-a-time if you want them to execute in sequence.

请注意,如果我没看错的话,您的某些异步功能可以简化一些;请记住,虽然await调用async函数会返回一个值,但简单地调用它会返回另一个诺言,而从async函数返回一个值与返回一个在非运算符中解析为该值的诺言相同-async功能.因此,例如,_get可以是:

As a side note, if I'm reading them right, some of your async functions can be simplified a bit; remember that, while awaiting an async function call returns a value, simply calling it returns another promise, and returning a value from an async function is the same as returning a promise that resolves to that value in a non-async function. So, for example, _get can be:

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主体同时执行):

Similarly, _scan could be (assuming you want the forEach bodies to execute concurrently):

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;
  }
}

这篇关于异步/等待不等待的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆