【问题标题】:How to push the data returned by a promise into an array?如何将 promise 返回的数据推送到数组中?
【发布时间】:2020-08-25 17:31:06
【问题描述】:

我正在调用 Udemy API。为了同时拨打电话,我使用了一个循环。通过这样做,我会自动增加页码并尝试从每个页面中获取数据并将其存储到一个数组中,以便我可以将所有数据以 json 格式写入单个文件中。但我得到的只是一个空数组。如何访问 promise 返回的值并存储到 doc.table 数组中?

我的代码:

const fetch=require("node-fetch");
const fs=require("fs");
let doc={};
doc.table=[];

for(let i=1;i<=10;i++){

fetch('https://www.udemy.com/api-2.0/courses/ page='+i+'&page_size=10&client_id=${client_id}&client_secret=${client_secret},{
      method:'GET',
      body:null,
      headers:{authorization: ${auth_code}}
      })
      .then(res=>res.json())
      .then(json=>doc.table.push(json))
};


fs.writeFile("UDEMY.json",JSON.stringify(doc),function(err){
    if(err) throw err;
    console.log("Complete");
});

【问题讨论】:

  • 由于 promises 是异步的,所以在使用任何结果之前,您需要等待它们解决 - 最简单的方法是使用 async/await,然后等待 fetch - 当然,代码将需要在 async function ... 或只是在 (async () =&gt; { ... all the code from for loop to all the writefile code ...})();
  • 您发布的代码中有两件事要注意......您似乎想使用模板字符串,但没有正确使用,并且不需要;关闭}一个for循环

标签: javascript node.js json es6-promise fetch-api


【解决方案1】:

我建议使用await,这样每次迭代都会暂停for 循环:

const fetch = require("node-fetch");
const fsp = require("fs").promises;

let doc = { table: []};

async function run() {
    for (let i = 1; i <= 10; i++) {

        let data = await fetch(`https://www.udemy.com/api-2.0/courses?page=${i}&page_size=10&client_id=${client_id}&client_secret=${client_secret}`,{
              method:'GET',
              body:null,
              headers:{authorization: auth_code}
        }).then(res=>res.json());

        doc.table.push(data);
    }

    await fsp.writeFile("UDEMY.json",JSON.stringify(doc));
    console.log("done");
}

run().catch(err => {
    console.log(err);
});

另一种可能性是并行运行所有请求并使用Promise.all() 知道它们何时全部完成。这两种解决方案的关键是使用fetch() 返回来控制知道事情何时完成的承诺。

如果你真的想并行运行它们并且你确定你的目标主机会允许它,你可以这样做:

const fetch = require("node-fetch");
const fsp = require("fs").promises;

let doc = { table: []};

function run() {
    let promises = [];
    for (let i = 1; i <= 10; i++) {

        promises.push(fetch(`https://www.udemy.com/api-2.0/courses?page=${i}&page_size=10&client_id=${client_id}&client_secret=${client_secret}`},{
              method:'GET',
              body:null,
              headers:{authorization: ${auth_code}}
        }).then(res=>res.json()));

    }
    return Promise.all(promises).then(data => {
        doc.table = data;
        return fsp.writeFile("UDEMY.json",JSON.stringify(doc));
    });

}

run().then(() => {
    console.log('done');
}).catch(err => {
    console.log(err);
});

而且,如果您想要某种级别的并行请求,但又想限制并行请求的数量,您可以使用mapConcurrent() 描述的here

【讨论】:

  • 一些 API 无论如何都不喜欢太多的并行请求,因此,串联执行它们可能会更好(我认为 OP 认为它们无论如何都是串联运行的)
  • @NishanthB.S - 正如我所说,如果您想使用Promise.all() 并拥有更简洁的设计,您可以并行运行您的请求。一些主机不允许你启动 100 个并行请求(你会受到速率限制,或者主机崩溃或导致主机运行非常非常低效)。
  • @NishanthB.S - 我添加了一个并行选项,使用 Promise 来管理控制流。
  • @NishanthB.S - 另请注意,这两个选项都可以在一个中心位置为您提供干净的错误处理。这是使用带有 Promise 的良好设计模式的另一个优势。
  • @jfriend00 是的,明白了!在您使用 async await 的早期代码中,虽然执行时间有所延迟,但在写入文件时没有丢失任何数据。我认为这更重要。感谢您的回复!
【解决方案2】:

您可以尝试检查当前循环索引,并在最后一个 Promise fullfillment 中写入您的文件:

const fetch = require('node-fetch');
const fs = require('fs');

let url;
let counter = 10;
const doc = {
  table: []
};

for (let i = 1; i <= 10; i++) {
  url = `https://www.udemy.com/api-2.0/courses/page=${i}&page_size=10&client_id=${client_id}&client_secret=${client_secret}`;
  fetch(url, {
    method: 'GET',
    body: null,
    headers: {
      authorization: auth_code
    }
  })
  .then(res => res.json())
  .then(json => {
    // next line will not guarantee the order of pages
    // doc.table.push(json);
    // so we can use current loop index and counter
    doc.table[i] = json;
    // when counter is 0 we can write a file 
    if (!--counter) {
      fs.writeFile('UDEMY.json', JSON.stringify(doc), function(err) {
        if (err) {
          throw err;
        }
        console.log("Complete");
      });
    }
  })
};

我还修复了您的 URL 模板字符串中的小错误...

【讨论】:

  • 当然,这并不能保证doc.table中数据的顺序,也不能保证所有数据都存在,因为当i===9时,可能需要很长时间i===10 的结果在 i===9 的结果之前返回 - 最好做 doc.table[i-1] = json 并且还有一个结果计数器(这里也不能使用数组长度)并在收到 10 个结果后继续
  • 在操作码sn-p命令无论如何都没有保存,如果命令很重要我们可以使用doc.table[i] = json;。我会更新我的答案,谢谢指点...
  • 我认为 OP 不理解承诺链,因此他认为这一切都是在 for 循环之后完成的,因此认为每个循环都将串联完成,而不是并行完成:p
  • @jfriend00 是的。会记住这一点。感谢您的建议!
  • jfriend00,感谢您在我的回答中删除了关于反模式的反馈(从 2 小时前开始)。保持异步fetch调用异步并没有错,只有在满足条件时才以异步方式执行所需的回调。这种方式渲染不会被阻塞。如果我们将 10 次异步 fetch 调用转换为同步,浏览器渲染和其他预定的 JS 操作将被阻塞,直到所有 10 次调用成功或失败。 fs.writeFile() 也是异步的,而 await fsp.writeFile() 是同步的,因此再次阻塞代码执行和渲染
【解决方案3】:

如果订单很重要,您还可以将 Promise 保存在数组中,并在每个 Promise 完成后访问它们

const promises = []
promises.push(...)
Promise.all(promises).then(data -> ....)

data 现在将是一个包含各个承诺结果的数组。您可以随意合并或处理它们。 请注意,上述函数只有在所有先前的承诺都解决后才会解决。

【讨论】:

    猜你喜欢
    • 2019-03-07
    • 2015-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多