Загружать видео последовательно одно за другим в Node JS, используя асинхронность?

Я хочу скачивать видео одно за другим в серии.

То есть первый должен быть полностью загружен до запуска второго, второй должен быть полностью загружен до запуска третьего и так далее.

У меня есть следующая структура каталогов -

video-downloader
├── index.js
├── videos.js
├── package.json

пакет.json

{
  "name": "video-downloader",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "download": "^7.1.0"
  },
  "scripts": {
    "start": "node index"
  }
}

видео.js

const videos = [
  {
    url: 'https://video.com/lesson1.mp4',
    name: 'Lesson 1',
  },
  {
    url: 'https://video.com/lesson2.mp4',
    name: 'Lesson 2',
  },
  .
  .
  .
  {
    url: 'https://video.com/lesson2.mp4',
    name: 'Lesson 100',
  }
]

index.js

const fs = require('fs')
const download = require('download')

const videos = require('./videos')

const OUTPUT_DIR = 'Downloads'

fs.mkdir(OUTPUT_DIR, () => {
   main()
})

const main = () => {
    videos.map((video, i) => {
        console.info(`Downloaded file ${i + 1} of ${videos.length} (${video.name})`)
        download(video.url).pipe(
            fs.createWriteStream(`${OUTPUT_DIR}/${video.name}.mp4`),
        )
    })
}

Это загружает видео по частям параллельно. Все видео загружаются одновременно, но ни одно из них не завершается до начала другого.

Как скачать серийно?

Я знаю, что должен использовать что-то вроде http://caolan.github.io/async/, но для этого нужна подпись функции, и у меня есть videos в виде массива, поэтому я не уверен, как это сделать.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
380
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Попробуйте асинхронное ожидание для этого. Сначала загрузите, а затем запишите в Sync.

const fs = require('fs');
const sh = require('shelljs');
const download = require('download');

const videos = require('./videos');

const OUTPUT_DIR = 'Downloads';

sh.mkdir('-p', OUTPUT_DIR);

videos.forEach(async (video, i) => {
  console.info(`Downloading ${video.name}. Fil${i + 1}/${videos.length} - `);
  const data = await download(video.url);
  fs.writeFileSync(`${OUTPUT_DIR}/${video.name}.mp4`, data);
});

спасибо, это работает, но загружается в случайном порядке. Я хочу, чтобы это было сделано в указанном порядке :)

deadcoder0904 13.06.2019 08:10

Это не работает, вы получите массив из 100 объектов промисов, а загрузка все еще находится в режиме parelel.

Ferrybig 13.06.2019 08:14

map не нужен, forEach будет работать, массив промисов нам не нужен. он будет загружать один контент в каждой итерации цикла. проверить порядок массива видео.

Arshpreet Wadehra 13.06.2019 08:17

действительно это работает, но загружается в случайном порядке. даже с forEach. 1-е решение с использованием цикла for работает отлично :)

deadcoder0904 13.06.2019 09:50

Вы можете использовать .reduce с обещаниями для последовательного разрешения следующим образом:

const fs = require('fs')
const sh = require('shelljs')
const download = require('download')

const videos = require('./videos')

const OUTPUT_DIR = 'Downloads'

sh.mkdir('-p', OUTPUT_DIR)

videos = videos.reduce((acc, item) => {

  return acc.then(() => {
    return new Promise((resolve) => {

      // Here you are using it as a Duplex Stream, not a promise,
      // therefore, you must check when the stream emits the 'end' event
      // so you can proceed further
      let stream = download(video.url)
        .pipe(fs.createWriteStream(`${OUTPUT_DIR}/${video.name}.mp4`));

      stream.on('end', () => {
        console.info(`stream done ${item}`);
        resolve(item);
      })

    })

  });

}, Promise.resolve());

// 'videos' is now a promise
videos.then((lastPromise) => {

  // using reduce will return the last evaluated item(promise)
  // but reaching the last one means the promises before that have been resolved

  console.info('all files were downloaded');


})

я не пробовал это, так как первый сработал для меня, но есть голос и спасибо за ответ :)

deadcoder0904 13.06.2019 09:51
Ответ принят как подходящий

Вы можете использовать ключевое слово await в стандартных циклах for, и все будет обрабатываться по порядку и ждать каждой загрузки, прежде чем продолжить.

const fs = require('fs')
const download = require('download')
const videos = require('./videos')
const util = require('util')

const mkdirAsync = util.promisify(fs.mkdir)

const OUTPUT_DIR = 'Downloads'

const main = async () => {
  await mkdirAsync(OUTPUT_DIR)

  for (let i = 0; i < videos.length; i++) {
    const video = videos[i]
    const data = await download(video.url)
    fs.writeFileSync(`${OUTPUT_DIR}/${video.name}.mp4`, data)
    console.info(`Downloaded file ${i + 1} of ${videos.length} (${video.name})`)
  }
}

main()

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

jameslol 13.06.2019 10:35

какая разница? Я имею в виду, что теперь он работает отлично.

deadcoder0904 13.06.2019 14:50

Функционально ничем не отличается. Я, вероятно, перегнул палку, предложив это, извините. Но, учитывая, что я уже предложил это... Вы используете две разные асинхронные методологии в одном фрагменте кода - обратный вызов и асинхронный/ожидающий. Если вы придерживаетесь одного, это снижает умственную нагрузку на размышления. Пример обещания fs.mkdir сам по себе был немного надуманным, но как только вы добавите больше асинхронных действий, будет намного проще рассуждать о том, является ли это только асинхронным/ожиданием или только обратными вызовами.

jameslol 14.06.2019 05:09

о нет, мне просто было интересно. не нужно сожалеть. Я понял. Я также склонен использовать любой из обоих, и мне больше нравится async/await без try-catch, и я склонен писать меньше LOC, поэтому я оставлю свой, пока он не доставит мне проблем. Тогда наверное попробую урс. Спасибо еще раз :)

deadcoder0904 14.06.2019 10:19

Другие вопросы по теме