【问题标题】:Using promises in JS /Node JS it loops through every object every time在 JS /Node JS 中使用 promise 每次都会遍历每个对象
【发布时间】:2020-02-25 00:59:18
【问题描述】:

我正在尝试制作一个程序来验证链接是否损坏或工作。我是 JS 的新手,也是 Promise 的新手。我正在测试一个包含一个损坏的链接和一个工作的降价文件,显然,每次我运行代码时它都会运行两次(每个链接一个,如果我添加 3 个链接它会运行 3 次)。

这是我收到的输出

result

这是我的代码

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

const inputPath = process.argv[2];
const inputOptions = process.argv[3];
const inputOptionsTwo = process.argv[4];

let okLinks = [];
let okLinksCount = 0;
let notOkLinks = [];
let notOkLinksCount = 0;


const checkFilePath = () => {
    let pathExt = path.extname(inputPath);
    if (pathExt == '.md') {
        console.log('md file')
        parseFile(inputPath);
    } else {
        console.log('file not recognized');
    }
};




const parseFile = (inputPath) => {
    fs.readFile(inputPath, 'utf8', (err, data) => {
        if (!err) {
            const regex = new RegExp(/(https?:\/\/[^\s\){0}]+)/g);
            const links = data.match(regex);
            if (links) {
                //function to validate, pass the links as parameter
                validateLinks(links);
            } else {
                console.log('no links found');
            }
        } else {
            //error reading files
            console.log('an error ocurred');
            console.error(error.message);
        }
    });
};


const validateLinks = (links) => {

    for (let i = 0; i < links.length; i++) {
        const p = new Promise(resolve => {

            fetch(links[i])
                .then(res => {

                    if (res.status >= 400) {
                        notOkLinksCount++;
                        notOkLinks.push(links[i] + ' FAIL : ' + res.status);
                    } else {
                        okLinks.push(links[i] + ' OK : ' + res.status);
                        okLinksCount++;

                    }
                    console.log('f');

                    if (inputOptions === '--validate') {
                        setTimeout(function() {
                            console.log(notOkLinks);
                            console.log(okLinks);
                        }, 500);
                    } else if (inputOptions === '--stats' && inputOptionsTwo === '--validate') {
                        setTimeout(function() {
                            console.log('Total: ' + links.length + '\n' + 'Ok: ' + okLinksCount);
                            console.log('Broken: ' + notOkLinksCount);
                            console.log(notOkLinks);
                            console.log(okLinks);
                        }, 2800);
                    } else if (inputOptions === '--stats') {
                        setTimeout(function() {
                            console.log('Total: ' + links.length + '\n' + 'Ok: ' + okLinksCount);
                        }, 2800);
                    }
                }).catch((error) => {
                    console.error('Error');
                });


        })
    }

}




checkFilePath();

【问题讨论】:

  • 您确定它运行两次,而不仅仅是登录控制台两次?
  • validateLinks() 的实现有点奇怪,这让我想知道这是否是您感到困惑的部分原因。这是一个for 循环(遍历所有链接),然后是fetch() 每个链接。但是,整个循环都包含在一个 Promise 中,但是您永远不会解决该 Promise,因此您无法知道所有 fetch() 操作何时完成,事实上,所有 fetch() 操作都在并行运行并且将以随机顺序完成(因此以随机顺序登录)。这可能会导致您的输出日志的阅读混乱。
  • 而且,代码包含许多 setTimeout() 函数,它们只记录数据,再次进一步混淆输出,因此您无法知道所有输出的时间。
  • 您可以在您的环境中使用await/async 语法吗?它应该可以更容易地理解承诺的代码。

标签: javascript node.js loops promise


【解决方案1】:

如果您能够在您的环境中使用async/await 语法,那么它将使promise 的推理更容易。

目前代码不会等待异步代码完成,这就是我认为添加setTimeouts 的原因。像这样使用它们是不稳定的,因为它们是在猜测代码需要多长时间才能完成,而不是真正等到它完成或出错。

尽量避免将回调混入诸如fs.readFile之类的promise代码中,Node提供了fs promise API now

Bluebird promise library 提供了一些帮助器,Promise.mapPromise.filter 用于处理数组。还有Promise.delay 如果您确实需要使用setTimeout 出于其他原因。

结合所有这些意味着对代码进行相当多的重组。

const fsp = require('fs').promises;
const fetch = require('node-fetch');
const Promise = require('bluebird');

const parseFile = async (inputPath) => {
    try {
        const data = await fsp.readFile(inputPath, 'utf8')
        const regex = new RegExp(/(https?:\/\/[^\s\){0}]+)/g);
        const links = data.match(regex);
        if (!links) {
            return console.log('no links found');
        } 
        //function to validate, pass the links as parameter
        return validateLinks(links);
    } catch (error) {
        //error reading files
        console.error('An error occurred processing '+inputPath);
        throw error
    }
};

const checkLink = async (link) => {
    try {
        const res = await fetch(link)
        if (res.status >= 400) {
            const error = new Error(`'Request failed for ${link} with ${res.status}`)
            error.res = res
            error.status = status
            throw error
        }
        return link;
    }
    catch (error) {
        error.link = link
        throw error
    }
}

const validateLinks = async (links) => {
    const notOkLinks = [];
    const okLinks = await Promise.filter(links, async link => {
        try {
            return checkLink(link)
        }
        catch (error) {
            console.error('Error for %s', link, error);
            notOkLinks.push(error)
            return false
        }
    })

    return {
        okLinks,
        notOkLinks,
    }
}

然后您可以重新实现checkFilePathawait parseFile() 并处理对象中返回的okLinksnotOkLinks 的日志记录。删除所有 setTimeouts,因为 await 将等待承诺完成后再继续。

【讨论】:

    【解决方案2】:

    试试这个。您不应该使用超时,使用 promise.all 来等待处理完成(或异步等待)。代码中有多个日志的原因是您在创建 Promise 的循环中创建了日志记录超时,因此您为每个链接创建了一个日志函数,而不仅仅是一次。 p>

    const path = require('path');
    const fs = require('fs');
    const fetch = require('node-fetch');
    
    const inputPath = process.argv[2];
    const inputOptions = process.argv[3];
    const inputOptionsTwo = process.argv[4];
    
    let okLinks = [];
    let okLinksCount = 0;
    let notOkLinks = [];
    let notOkLinksCount = 0;
    
    const checkFilePath = () => {
      let pathExt = path.extname(inputPath);
      if (pathExt === '.md') {
        console.log('md file');
        parseFile(inputPath);
      } else {
        console.log('file not recognized');
      }
    };
    
    const parseFile = (fp) => {
      fs.readFile(fp, 'utf8', (err, data) => {
        if (!err) {
          const regex = new RegExp(/(https?:\/\/[^\s\){0}]+)/g);
          const links = data.match(regex);
          if (links) {
            // function to validate, pass the links as parameter
            validateLinks(links);
          } else {
            console.log('no links found');
          }
        } else {
          // error reading files
          console.log('an error ocurred');
          console.error(err.message);
        }
      });
    };
    
    const validateLinks = (links) => {
      let promises = [];
      for (let i = 0; i < links.length; i++) {
        promises.push(
          fetch(links[i]).then(res => {
            if (res.status >= 400) {
              notOkLinksCount++;
              notOkLinks.push(links[i] + ' FAIL : ' + res.status);
            } else {
              okLinks.push(links[i] + ' OK : ' + res.status);
              okLinksCount++;
            }
            console.log('f');
          }).catch((error) => {
            console.error(error);
          })
        );
      }
      // eslint-disable-next-line no-loop-func
      Promise.all(promises).then(() => {
        if (inputOptions === '--validate') {
          console.log(notOkLinks);
          console.log(okLinks);
        } else if (inputOptions === '--stats' && inputOptionsTwo === '--validate') {
          console.log('Total: ' + links.length + '\n' + 'Ok: ' + okLinksCount);
          console.log('Broken: ' + notOkLinksCount);
          console.log(notOkLinks);
          console.log(okLinks);
        } else if (inputOptions === '--stats') {
          console.log('Total: ' + links.length + '\n' + 'Ok: ' + okLinksCount);
        }
      });
    };
    
    
    checkFilePath();
    

    【讨论】:

    • 谢谢!我一直在尝试使用 promise.all 但不太明白它是如何工作的,但这很棒,谢谢!
    【解决方案3】:

    它每次都会遍历每个对象

    不完全是。问题出现在validateLinks(),本质是这样的:

    const validateLinks = (links) => {
        for (let i = 0; i < links.length; i++) {
            const p = new Promise(resolve => {
                fetch(links[i]).then(res => {
                    if (res.status >= 400) {
                        notOkLinks.push(links[i] + ' FAIL : ' + res.status);
                    } else {
                        okLinks.push(links[i] + ' OK : ' + res.status);
                    }
                    console.log(notOkLinks); // <<<<<
                    console.log(okLinks); // <<<<<
                }).catch((error) => {
                    console.error('Error');
                });
            })
        }
    }
    

    这里有两个问题:

    1. 两个console.log() 语句在.push() 语句之后立即执行,这为时过早。它们应该仅在所有异步活动完成后执行 - 即当 for 循环中生成的所有 Promise 都已解决时。
    2. validateLinks() 应该返回一个聚合承诺,以便通知函数的调用者异步完成。

    就我个人而言,我会将所有 Promise 链接逻辑和结果记录放在最高级别的函数 checkFilePath() 中,并将 parseFile()validateLinks() 降级为低级别工作人员。

    const path = require('path');
    const fs = require('fs');
    const fetch = require('node-fetch');
    
    const inputPath = process.argv[2];
    const inputOptions = process.argv[3];
    const inputOptionsTwo = process.argv[4];
    const checkFilePath = () => {
        let pathExt = path.extname(inputPath);
        if (pathExt == '.md') {
            console.log('md file');
            // Form the entire promise chain here.
            return parseFile(inputPath)
            .then(validateLinks)
            .then(validated => {
                // `validated` is an array of "inspection objects" - either {link, res} or {link, error}.
                // Compose the `okLinks` & `notOkLinks` arrays ...
                const okLinks = validated.filter(o => !o.error).map(o => `${o.link} OK: ${o.res.status}`);
                const notOkLinks = validated.filter(o => !!o.error).map(o => `${o.link} FAIL: ${o.error.message}`);
                // ... and log the results
                if (inputOptions === '--validate') {
                    console.log(notOkLinks);
                    console.log(okLinks);
                } else if (inputOptions === '--stats' && inputOptionsTwo === '--validate') {
                    console.log('Total: ' + validated.length + '\n' + 'OK: ' + okLinks.length);
                    console.log('Broken: ' + notOkLinks.length);
                    console.log(notOkLinks);
                    console.log(okLinks);
                } else if (inputOptions === '--stats') {
                    console.log('Total: ' + validated.length + '\n' + 'OK: ' + okLinks.length);
                } else {
                    console.log('invalid inputOptions');
                }
            });
        } else {
            console.log('file not recognized');
        }
    };
    const parseFile = async (inputPath) => {
        return new Promise((resolve, reject) {
            fs.readFile(inputPath, 'utf8', (err, data) => {
                if (err) {
                    reject(err);
                } else {
                    const links = data.match(new RegExp(/(https?:\/\/[^\s\){0}]+)/g));
                    if (links && links.length) {
                        resolve(links);
                    } else {
                        reject(new Error('no links found'));
                    }
                }
            });
        });
    };
    const validateLinks = async (links) => {
        // map `links` to an array of Promises and aggregate with Promise.all()
        return Promise.all(links.map(link => {
            return fetch(link)
            .then(res => {
                if(res.status >= 400) {
                    throw new Error(res.status);
                } else {
                    return { link, res }; // validated
                }
            })
            .catch(error => {
                // expected and unexpected errors will end up here
                return { link, error }; // not validated
            });
        }));
    }
    checkFilePath();
    

    【讨论】:

      猜你喜欢
      • 2014-06-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-28
      • 1970-01-01
      • 2018-07-04
      相关资源
      最近更新 更多