【问题标题】:How to use async, await and promises?如何使用异步、等待和承诺?
【发布时间】:2020-04-22 13:42:31
【问题描述】:

我正在构建一个网络爬虫来获取所有用户在 codeforces 上提交的内容。 我不太了解异步、等待、承诺。 我使用 axios(基于promise)请求codeforces 和cheerio 来解析HTML。

app.post("/", (req, res) => {
  const usernameorhandle = req.body.userName;
  getstatus(usernameorhandle).then ( ()=> {
      var output = fs.createWriteStream(__dirname + '/Data/solutions.zip');
      var archive = archiver('zip', {
        zlib: { level: 9 } // Sets the compression level.
      });
      output.on('close', function() {
        console.log(archive.pointer() + ' total bytes');
        console.log('archiver has been finalized and the output file descriptor has closed.');
      });
      output.on('end', function() {
        console.log('Data has been drained');
      });
      res.attachment(__dirname + "/Data/Problems", 'Codeforces-Solutions');
      archive.pipe(res);
      archive.directory(__dirname + "/Data/Problems", 'Codeforces-Solutions');
      archive.finalize();
    }) })

我用来接受发帖请求。 我将所有解决方案放入一个文件夹并创建 zip 文件夹,然后发送到 res。

下面是我的getstatus函数。

    async function getstatus(handle){
  return new Promise(async (resolve, reject)=> {
    console.log("HELLLLLLLOOOOOOOO");
    await axios.get("https://codeforces.com/api/user.status?handle=" + handle + "&from=1")
      .then(response => {
        if(response.data.status === 'OK'){
          let results = response.data.result;
          console.log("AAAAAAAAAAAAAAAAAAAAAAAa");
          scrape(results).then( () =>{
            console.log("DONE");
            resolve();
          })
          .catch(err => console.log(err));
          // resolve();
        }
        else console.log(submissions.comment);
      })
  })

}

我使用scrape函数来获取HTML数据并放到名为Problems的文件夹中。

async function scrape (results){
  console.log("inside scrape");
  //  console.log("HELLO");
  return new Promise( async (resolve, reject) => {
    await results.forEach(async (result)=> {
      if(result.verdict === 'OK'){
        await axios.get("https://codeforces.com/contest/" + result.contestId + "/submission/" + result.id)
        .then(solutionPage => {
          const $ = cheerio.load(solutionPage.data);
          const path = "/home/srujan/Desktop/crawlerapp/Data/Problems/" + result.problem.name + ".cpp";
           fs.writeFile(path, $('#program-source-text').text(), function(err){
            if(err){
              console.log(err);
            }
            else{
              console.log("Saved file");
            }
          })
        })
        .catch( error => {
          console.log("HTML PARSE ERROR" + error);
        })
     }
    })
    console.log("hey");
    resolve();

  })

问题是我得到了

HELLLLLLLOOOOOOOO
AAAAAAAAAAAAAAAAAAAAAAAa
inside scrape
hey
DONE
saved file
saved file
...

DONE 后浏览器下载,然后保存文件。 我是 js 新手,不知道为什么会这样。

PS:我知道这是一个很长的问题。我尝试阅读很多关于此的内容。没有正确理解如何做到这一点。我复制粘贴了一些我不明白的代码,例如如何压缩文件夹。

【问题讨论】:

  • 如果你想了解 async/await 和 promises,MDN docs 非常有用。
  • 我读过,但我不知道我的代码有什么问题。
  • 此外,res.attachment() 方法在您的响应中设置 Content-Disposition 标头,这会使您的浏览器下载文件。所以这是预期的行为。这不是你想要达到的吗?
  • 是的,它会在完全收到响应后立即开始下载,因为响应具有 Content-Disposition 标头。
  • await 关键字将为您提供已解决承诺的结果,一旦解决。它等待承诺解决,然后程序的其余部分才会继续。

标签: javascript node.js web-scraping promise async-await


【解决方案1】:

forEach(callback) 执行 callback。如果callback 返回一个promise(即它是一个异步函数),则在调用数组的下一个元素的回调之前不会解析promise。

因此,基本上,您不能在 forEach 中使用异步函数...但是您可以使用 for-loops 或 Promise.all

另外,fs.writeFile 与同步 + 回调一起使用,但存在一个 fs.promise.writeFile 使用 Promise 代替。

这里有一个更好的抓取功能:

async function scrape(results) {
  for (const result of results) {
    if(result.verdict === 'OK') {
      const solutionPage = await axios.get("https://codeforces.com/contest/" + result.contestId + "/submission/" + result.id);
      const $ = cheerio.load(solutionPage.data);
      const path = "/home/srujan/Desktop/crawlerapp/Data/Problems/" + result.problem.name + ".cpp";
      try {
        await fs.promises.writeFile(path, $('#program-source-text').text());
      } catch(err) { console.log(err) }
    }
  }
}

【讨论】:

  • 但是浏览器在写入文件之前正在下载,并且在写入之后又再次下载?第一次下载没有内容,第二次完美。为什么会这样?
【解决方案2】:

问题是使用 result.forEach 尝试使用不带异步的简单 for(let i = 0; i

如果这不起作用,请尝试在 then 中返回任何内容。

【讨论】:

    【解决方案3】:

    这就是我将如何使用 await async 构造 getstatus 函数

     async function getstatus(handle) {
    
        const response = await axios.get("https://codeforces.com/api/user.status?handle=" + handle + "&from=1")
    
        if(response.data.status === 'OK') {
    
          let results = response.data.result;
    
          try {
            await scrape(results);
            console.log("DONE");
          }
          catch(error) {
          }
    
        }
    }
    

    scrape 相应地起作用......

    const fs = require('fs').promises;
    
    async function scrape (results) {
      results.forEach(async (result)=> {
    
       if(result.verdict === 'OK') {
        const solutionPage = await axios.get("https://codeforces.com/contest/" + result.contestId + "/submission/" + result.id)
    
        const $ = cheerio.load(solutionPage.data);
        const path = "/home/srujan/Desktop/crawlerapp/Data/Problems/" + result.problem.name + ".cpp";
    
        try {
          await fs.writeFile(path, $('#program-source-text').text())
          console.log("Saved file");
        }
        catch(error) {
        }
      }
     }
    }             
    

    【讨论】:

    • fs.writeFile 需要回调,所以我们不能使用 try catch。使用回调时,我们也不能在 fs.writeFile 之前使用 await。在写入文件之前收到发布请求后,浏览器也会突然下载。
    • 您可以使用util.promisify() 将带有回调的函数更改为promise,或者查看文档是否有类似promise 的变体,此处为require('fs').promises。好吧,我不确定后者是否仍受支持
    猜你喜欢
    • 2017-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-02
    • 1970-01-01
    • 1970-01-01
    • 2019-03-20
    • 2021-11-09
    相关资源
    最近更新 更多