【问题标题】:nodeJS: how to call an async function within a loop in another async function callnodeJS:如何在另一个异步函数调用的循环中调用异步函数
【发布时间】:2019-07-16 04:56:34
【问题描述】:

我试图从另一个异步函数运行的循环中调用一个异步函数。这些函数调用 API,我使用 request-promisenodeJS

functions.js 文件

const rp = require("request-promise");


// function (1)
async email_views: emailId => {
    let data = {};
    await rp({
       url: 'myapiurl',
       qs: { accessToken: 'xyz', emailID: emailId },
       method: 'GET'
    })
      .then( body => { data = JSON.parse(body) })
      .catch( error => { console.log(error} );

    return data;
};

上面的 JSON 看起来像这样:

...
data:{
   records: [
      {
        ...
        contactID: 123456,
        ...
      },
      {
        ...
        contactID: 456789,
        ...
      }
   ]
}
...

我正在运行一个循环来获取单个记录,其中我得到一个与每个记录相关联的contactID

// function#2 (also in functions.js file)
async contact_detail: contactId => {
    let data = {};
    await rp({
       url: 'myapiurl2',
       qs: { accessToken: 'xyz', contactID: contactId },
       method: 'GET'
    })
      .then( body => { data = JSON.parse(body) })
      .catch( error => { console.log(error} );

    return data;

};

上述函数将contactId 作为参数,并通过调用另一个API 端点获取该联系人的详细信息。

这两个函数在单独调用时都能正常工作。但我试图在这样的循环中做到这一点:

...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
for( let i=0; i<records.length; i++) {
    ...
    const cId = records[i].contactID;
    const contact = await contact_detail(cId); // function#2
    names += contact.data.firstName + " " + contact.data.lastName + " ";
    ...
}
console.log(names);
...

问题是我只从上面的代码块中得到第一个 contact,即即使我有来自 function#1 的 20 条记录,在我调用 contact_detail 时的循环中(函数#2) 对于每个contactID (cId),我会获得一次联系方式,即仅针对第一个cId。休息我什么都得不到!

使用nodeJs 实现此目的的正确方法是什么?

更新:

const { App } = require("jovo-framework");
const { Alexa } = require("jovo-platform-alexa");
const { GoogleAssistant } = require("jovo-platform-googleassistant");
const { JovoDebugger } = require("jovo-plugin-debugger");
const { FileDb } = require("jovo-db-filedb");

const custom = require("./functions");
const menuop = require("./menu");
const stateus = require("./stateus");
const alexaSpeeches = require("./default_speech");
const app = new App();

app.use(new Alexa(), new GoogleAssistant(), new JovoDebugger(), new FileDb());

let sp = "";
async EmailViewsByContactIntent() {
    try {
      const viewEmailId =
        this.$session.$data.viewEmailIdSessionKey != null
          ? this.$session.$data.viewEmailIdSessionKey
          : this.$inputs.view_email_Id_Number.value;
      let pageIndex =
        this.$session.$data.viewEmailPageIndex != null
          ? this.$session.$data.viewEmailPageIndex
          : 1;

      const result = await custom.email_views_by_emailId(
        viewEmailId,
        pageIndex
      );
      const records = result.data.records;
      if (records.length > 0) {
        const totalRecords = result.data.paging.totalRecords;
        this.$session.$data.viewEmailTotalPages = totalRecords;
        sp = `i have found a total of ${totalRecords} following view records. `;
        if (totalRecords > 5) {
          sp += `i will tell you 5 records at a time. for next 5 records, please say, next. `;

          this.$session.$data.viewEmailIdSessionKey = this.$inputs.view_email_Id_Number.value;
          this.$session.$data.viewEmailPageIndex++;
        }

        for (let i = 0; i < records.length; i++) {
          const r = records[i];

          /* Here I want to pass r.contactID as contactId in the function contact_detail like this: */

          const contact = await custom.contact_detail(r.contactID);
          const contact_name = contact.data.firstName + " " + contact.data.lastName;

         /* The above two lines of code fetch contact_name for the first r.contactID and for the rest I get an empty string only. */

          const formatted_date = r.date.split(" ")[0];
          sp += `contact ID ${spellOut_speech_builder(
            r.contactID
          )} had viewed on ${formatted_date} from IP address ${
            r.ipAddress
          }. name of contact is, ${contact_name}. `;
        }
        if (totalRecords > 5) {
          sp += ` please say, next, for next 5 records. `;
        }
      } else {
        sp = ``;
      }
      this.ask(sp);
    } catch (e) {
      this.tell(e);
    }
  }

我正在使用JOVO 框架和nodeJS 构建一个alexa 技能。

更新 #2 作为测试,我只返回了contactId,我将其传递给contact_detail 函数,并且在我的第一个UPDATE 下,我将正确的值返回到上述代码。

async contact_detail: contactId => {
   return contactId;
}

似乎即使在获得正确的值之后,该函数还是无法执行。但是,当我从另一个地方调用它时,相同的 contact_detail 函数可以正常工作。只有在循环中不起作用。

可能是什么原因? 我一定错过了什么,但不知道是什么!

【问题讨论】:

  • 我不确定返回数据的方式。我可能是错的,这是完全合法的。看到 done 函数和其他地方(即 return 语句)之间的逻辑分裂,我只是感到紧张。我倾向于在 try catch 块中等待数据,或者可能只是返回承诺并执行 Promise.all() - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • 您的contact_detail() 函数调用没有显示任何实际使用您传递给它的参数的代码。如果它没有使用传入的contact_id,那么无论你通过什么,它总是会给你相同的结果。除此之外,我们需要查看for 循环中的其余代码,以了解哪里出了问题。
  • @jfriend00 我不确定您到底需要什么。根据您所说的我的理解,我已经更新了代码块和循环部分。请让我知道这是否有帮助。谢谢!
  • contactIDcontactId 有没有可能在某个地方混在一起?查询字符串中应该包含什么?我只是想看看为什么contact_detail(cId); 每次都会让你第一次联系。您的查询似乎有问题。
  • 很抱歉给您带来了困惑。它不是每次都第一次接触。我得到的是第一次说 Andrew 的联系人姓名,然后对于其余记录 (contactIDs) 我得到的是空值。

标签: node.js async-await request-promise


【解决方案1】:

您将 async await 和 promises 混合在一起,这导致您感到困惑。您通常会在给定位置使用其中之一(因为 async await 有效地提供了语法糖,因此您可以避免处理冗长的承诺代码)。

因为您将两者混为一谈,所以您处于一个难以确定行为的奇怪区域。

如果你想使用 async await,你的函数应该是这样的

async contact_detail: contactId => {
  try {
    const body = await rp({
       url: 'myapiurl2',
       qs: { ... }
    });

    return JSON.parse(body);
  } catch(e) {
    console.log(e);
    //This will return undefined in exception cases. You may want to catch at a higher level.
  }
};

或承诺

async contact_detail: contactId => {
    return rp({
       url: 'myapiurl2',
       qs: { ... }
    })
      .then( body => JSON.parse(body))
      .catch( error => { 
         console.log(error);
         //This will return undefined in exception cases. You probably dont want to catch here.
      });
};

请记住,您当前执行该函数的代码将依次执行每个调用。如果您想并行执行它们,则需要稍微不同地调用函数并使用 Promise.all 之类的东西来解析结果。

【讨论】:

  • 这是一个很好的风格推荐,但我认为它实际上并没有解决任何问题。
  • 这不仅仅是一个风格推荐。修改在异步范围之外定义的变量总是很麻烦。通常情况下,清理它可以解决问题。
  • 在这种特殊情况下,使用for 循环将数据收集到for 循环之前定义的变量中没有任何问题。这已经做了很多年了。没问题。即使是await。仍然没有问题。你还没有解释你为他们解决了什么实际问题。我同意有比这更好的样式可以使用,但我看不出你解决了什么问题。
【解决方案2】:

给你:

...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
await Promise.all(records.map(async record => {
    let cId = record.contactID;
    let contact = await contact_detail(cId);
    names += contact.data.firstName + " " + contact.data.lastName + " ";
});
console.log(names);
...

【讨论】:

  • 修改在异步范围之外定义的变量是不好的做法。在这种情况下,连接顺序将根据每个请求在contact_detail 中花费的时间而有所不同。更好的实现会返回contact.data.firstName + " " + contact.data.lastName + " ";并对 Promise.all 的解析执行 join
  • 这会将联系人置于未定义的顺序(基本上是随机的)。我也不确定它解决了什么问题。问题似乎是contact_detail(cId); 每次只返回第一个联系信息。
  • 它不工作。获取参考错误.length 未定义。目标是从records 中的每条记录中获取contactID,然后在另一个异步函数中将其传递给contactId,以获取该联系人的详细信息。对于第一个电话,我得到了正确的联系人姓名,但在休息 contactIDs 时,我得到了一个空 ('') 字符串。
【解决方案3】:

我将其发布为答案只是因为我需要向您展示一些多行代码作为解决此问题的一部分。不确定这是否能解决您的问题,但这是一个问题。

您的contact_detail() 函数未正确返回错误。相反,它会吃掉错误并使用空对象解决。这可能是导致您的空白名称的原因。它应该直接返回承诺,如果你想记录错误,那么它需要重新抛出。此外,没有理由将其声明为async 或使用await。您可以直接返回承诺。您也可以让 request-promise 为您分割 JSON 响应。

另外,我注意到,您的 .catch() 中似乎存在语法错误,这也可能是问题的一部分。

contact_detail: contactId => {
    return rp({
       url: 'myapiurl2',
       qs: { accessToken: 'xyz', contactID: contactId },
       json: true,
       method: 'GET'
    }).catch( error => { 
        // log error and rethrow so any error propagates
        console.log(error);
        throw error;
    });
};

然后,你会像原来一样调用它(注意你在调用它时仍然使用await,因为它返回一个承诺):

...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
for( let i=0; i<records.length; i++) {
    ...
    const cId = records[i].contactID;
    const contact = await contact_detail(cId);
    names += contact.data.firstName + " " + contact.data.lastName + " ";
    ...
}
console.log(names);
...

【讨论】:

  • 谢谢!这是我在循环内每次调用contact_detail 得到的结果:i have found a total of 230 following view records. i will tell you 5 records at a time. for next 5 records, please say, next. {"isFulfilled":false,"isRejected":false}{"isFulfilled":false,"isRejected":false}{"isFulfilled":false,"isRejected":false}{"isFulfilled":false,"isRejected":false}{"isFulfilled":false,"isRejected":false} please say, next, for next 5 records.
  • @SubrataSarkar - 你还在循环中做const contact = await contact_detail(cId) 吗?看起来不像你。这些看起来像是未决的承诺。
  • 我已经删除了await,因为您建议的主要功能不再是async。所以现在它显示为let contact = custom.contact_detail(r.contactID);
  • @SubrataSarkar - 把等待放回去。您的函数返回一个承诺。你需要 await 或 .then() 从 promise 中获取值。
  • @SubrataSarkar - 我在回答中添加了更多关于您如何使用await 称呼它的信息。
猜你喜欢
  • 2020-03-04
  • 1970-01-01
  • 2020-09-18
  • 2011-12-03
  • 2020-03-09
  • 1970-01-01
  • 1970-01-01
  • 2019-01-17
  • 2020-04-22
相关资源
最近更新 更多