Решение задачи при помощи асинхронных методов fs и setTimeout в JavaScript

Рейтинг: 1Ответов: 2Опубликовано: 19.03.2023

Первая часть задачи решена и заключалась в следующем

Сначала нужно будет получить информацию о файлах, которые лежат в папке files. В этом тебе помогут методы: fs.readdirSync(path) fs.statSync(path) А теперь для каждого файла нужно будет вызвать функцию loadFile (о том как её реализовать написано чуть ниже). После загрузки каждого файла тебе нужно будет обновлять свой progressbar. Чем больше размер файла тем больший кусок должен прибавляться к полоске прогрессбара. То есть, если у тебя есть файл размером 5Кб при общем размере всех файлов - 10 Кб, то твоя полоса загрузки после обработки такого файла должна заполниться на 50% (100% * (5Кб / 10Кб)). Теперь о том как реализовать функцию loadFile. Во-первых, тебе нужно будет прочитать файл используя метод fs.readFileSync. Однако, на современных компьютерах этот метод исполняется молниеносно. Поэтому для того чтобы увидеть как полоска прогрессбара постепенно заполняется, давай добавим искусственную задержку загрузки файла. Для этого тебе нужно будет воспользоваться синхронной версией setTimeout. Возьми её из пакета https://www.npmjs.com/package/wait-sync. Сделай таймаут пропорциональным размеру файла. Тут отталкивайся от размеров своих файлов. Например, можешь сделать так, чтобы 10 KB "загружались" 1 секунду.

Код я написал следующим образом:

const filesArr = fs.readdirSync("files", (err, data) => {
  if (err) throw err;
  return data;
});
        function loadFiles() {
      let count = [];
      for (let i = 0; i < filesArr.length; i += 1) {
        const size = fs.statSync(`files/${filesArr[i]}`).size;
        count.push(size);
      }
      const countNew = count.reduce((acc, el) => acc + el, 0);
      const countPercent = count.map((el) => +(el / countNew).toFixed(2));
      let counter = 0;
      for (let j = 0; j < countPercent.length; j += 1) {
        waitSync(0.9);
        counter += countPercent[j];
        bar.update(counter);
        console.log(count[j]);
      }
    }
    loadFiles();

Вторая часть задачи вызвала затруднения. Условия были следующими:

Теперь тебе нужно сделать всё то же самое, но пользуясь асинхронными методами fs, а также обычным асинхронным setTimeout в функции loadFile. Получить информацию о файлах в папке files тебе помогут fs-методы: fs.readdir(path) fs.stat(path) Файлы нужно будет загружать параллельно (то есть каждый следующий вызов loadFile должен произойти не дожидаясь того пока закончит свою работу предыдущий).

Как я понимаю функция loadFile должна содержать setTimeout, как в примере ниже:

function loadFile(array, callback) {
  setTimeout(() => {
    callback("Тут что-то происходит с каждым элементом array");
  }, 10);
}

Следующим этапом нужно получить массив, с которым можно работать. Но я не могу понять как работать fs.readdir(path) и fs.stat(path) (без Sync на конце). Асинхронные функции ничего не возвращают мне. То есть вариант

const filesArr = fs.readdir("files", (err, data) => {
      if (err) throw err;
      return data;
    });

возвратит мне undefined. Хочу разобраться, как в этом решении применить вместе setTimeout и асинхронные fs?

Ответы

▲ 0

Возможно не самый чистый код, но все же.

Вот вариант, как можно асинхронно считать файлы из директории и отобразить % загрузки файлов.

import fs from 'fs';

const path = 'c:/dev/stack/'; // Укажите свою директорию

fs.readdir(path, datafiles); // Читаем список файлов

// Функция, эмулирующая задержку чтения файла
async function sleep(delay) {
    return new Promise(res => {
        setTimeout(res, delay)
    });
}

async function datafiles(err, cb, name) {
    // Суммарный объем данных
    let len = 0;
    // Сколько файлов прочитано
    let readed = 0;
    // Инфа по файлам
    const files = {};
    // Получаем инфу по считанному файлу и записываем данные
    function getLen(err, data, name) {
        len += data.size;
        readed++;
        files[name] = data.size;
        if (readed === cb.length) showDownLoads();
    }
    // Перебираем список файлов и вызываем функцию для учета
    for (let i = 0; i < cb.length; i++) {
        fs.stat(path + cb[i], (err, data) => {
            getLen(err, data, path + cb[i]);
        });
    }
    // Выводим данные 
    async function showDownLoads() {
        console.log('BEGIN');
        let progress = 0;
        for (let i in files) {
            progress += files[i];
            fs.readFile(i, (arr, file) => {});
            await sleep(files[i]/10);
            console.log(`Скачен файл ${i}. Всего ${(progress / len * 100).toFixed(2)}%`);
        }
    }
}
▲ 0

Забыл упомянуть, что решить задачу нужно было без использования промисов и async-await (этот способ еще называют callback hell). Я придумал как сделать ее, но все же остался вопрос. Как обойтись без отдельной функции, которая считает размер всех файлов в одно число? Это число необходимо для расчета доли каждого файла в процессе загрузки, но я не нашел способа вычислить его в рамках главной функции loadFiles. Получилось просто использовать в loadFiles результат сторонней функции. Хотя может я хочу слишком многого. В аргумент files я подставляю путь до папки с 'загружаемыми' файлами.

function accumulate(file) {
  const filesArr = fs.readdirSync(file, (err, data) => {
    if (err) throw err;
    return data;
  });
  let count = [];
    for (let i = 0; i < filesArr.length; i += 1) {
      const size = fs.statSync(`${file}/${filesArr[i]}`).size;
      count.push(size);
    }
    const countNew = count.reduce((acc, el) => acc + el, 0);
    return countNew
}

function loadFiles(files) {
  setTimeout(() => {
    let asyncCounter = 0;
    let count = [];
    fs.readdir(files, (err, data) => {
      for (let i = 0; i < data.length; i++) {
        fs.stat(`./files/${data[i]}`, (err, stat) => {
          asyncCounter = asyncCounter + stat.size;
          bar.update(asyncCounter / accumulate(files));
          console.log(stat.size);
        });
      }
    });
  }, 1000);
}