Используя обещания в JS / Node JS, он каждый раз просматривает каждый объект - PullRequest
0 голосов
/ 25 февраля 2020

Я пытаюсь создать программу, которая проверяет, не работают ли ссылки. Я новичок в JS и новичок в обещаниях. Я тестирую файл уценки, который содержит одну неработающую ссылку и одну работающую, и, по-видимому, каждый раз, когда я запускаю код, он запускается дважды (по одной на каждую ссылку, если я добавляю 3 ссылки, он запускается три раза).

Это это вывод, который я получаю

результат

и это мой код

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

Ответы [ 3 ]

0 голосов
/ 25 февраля 2020

Попробуйте это. Вы не должны использовать тайм-ауты, используйте promise.all, чтобы дождаться завершения обработки (или asyn c await). Причиной нескольких журналов в вашем коде является то, что вы создаете таймауты регистрации внутри l oop, в котором вы создаете обещания, поэтому вы создаете функцию журнала для каждой ссылки, а не один раз.

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();
0 голосов
/ 26 февраля 2020

каждый раз проходит через каждый объект

Не совсем так. Проблема возникает в 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() заявления, что слишком рано. Они должны выполняться только после завершения всей асинхронной операции - ie, когда все Обещания, сгенерированные в for for l oop, выполнены.
  2. validateLinks() должны возвращать агрегированное обещание, чтобы разрешить Вызывающая функция должна быть проинформирована об асинхронном завершении.

Лично я бы поместил все логики цепочки обещаний c и запись результатов в функцию самого высокого уровня, 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();
0 голосов
/ 25 февраля 2020

Если вы можете использовать синтаксис async / await в своей среде, это облегчит рассуждение об обещаниях.

В настоящее время код не ожидает завершения асинхронного кода, поэтому я думаю, почему был добавлен setTimeouts. Используемые таким образом, они ненадежны, потому что они делают предположение о том, сколько времени займет выполнение кода, а не ожидают его завершения или ошибок.

Старайтесь не смешивать обратные вызовы с кодом обещания, например fs.readFile, Node предоставляет fs API обещаний .

Библиотека обещаний Bluebird предоставляет несколько помощников и Promise.map или Promise.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,
    }
}

Затем вы можете переопределить checkFilePath на await parseFile() и заняться регистрацией okLinks и notOkLinks, которые возвращаются в объекте. Удалите все setTimeout s, потому что await будет ждать обещаний до конца sh, прежде чем продолжить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...