Как правильно вызвать список асинхронных функций по порядку?

Я пытаюсь написать подобную "robocopy /mir" функцию в Node.js и не могу понять, как правильно выполнить несколько асинхронных функций по порядку.

Немного предыстории:

  • Скрипт запускается в Windows, поэтому мне нужно было найти способ копировать файлы, сохраняя время модификации и получая уведомления о прогрессе.
  • Чтобы решить эту проблему, я пошел дальше и написал свою функцию копирования в.NET (вызывая ее с помощью Edge.js)- эта функция копирования просто вызывает функцию Node, сообщающую о ходе копирования файла. Эта часть работает без нареканий.

Для того, чтобы файлы копировались по порядку, моей первой мыслью было сделать что-то вроде следующего:

Object.keys(filesToCopy).forEach(function(key) {
    var deferred = q.defer();
    var payload = {
        sourcePath: key,
        destPath: filesToCopy[key],
        progressCallback: progressCallback
    };

    console.log('Copying %s...', sourcePath);
    // Edge.js called here
    copyFile(payload, deferred.makeNodeResolver());

    deferred.promise.then(function(result) {
        console.log('%s complete.', result);
    }, function(err) {
        console.error('Error: ', err.message);
    });

    promises.push(deferred.promise);
});

К сожалению, это (как и ожидалось) начинает копировать каждый файл, как только вызывается функция.NET, поэтому я получаю уведомления о прогрессе для всех файлов сразу, давая мне вывод, например:

1%
2%
1%
2%
3%
3%

Похоже, мне нужен способ поставить в очередь работу, которую нужно выполнить, прежде чем запускать ее сразу, причем каждый элемент завершается до того, как будет продолжен следующий. Когда все пункты будут завершены, я должен быть уведомлен. Решение кажется достаточно простым, но оно по-прежнему ускользает от меня, так как все, что я пытаюсь сделать, связаны с другой проблемой. Любая помощь будет принята с благодарностью, спасибо!

РЕДАКТИРОВАТЬ: Как указано в моем комментарии, ответ Берги использовал функцию, которая фактически вернула обещание, в то время как моя функция Edge.js не сделала. Я смог решить мою проблему сначала, используя массив вместо объекта для filesToCopy затем делать что-то вроде этого:

  return filesToCopy.reduce(function(prev, curr) {
    return prev.then(function() {
      var deferred = q.defer();
      copyFile(curr, function(err, result) {
        deferred.resolve(result);
        console.log('Completed %s', result);
      });
      return deferred.promise;
    })
  }, q());

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

2 ответа

Решение

Может быть, что-то подобное поможет?

var $j = function(val, space) {
  return JSON.stringify(val, null, space || '')
}
var log = function(val) {
  document.body.insertAdjacentHTML('beforeend', '<div><pre>' + val + '</div></pre>')
}



var files = '12345'.split('').map(function(v) {
  return {
    name: 'file_' + v + '.js',
    load: function() {
      var cur = this;
      var pro = new Promise(function(resolve, reject) {

        log('loading : ' + cur.name);
        
        // we simualate the loading stuff
        setTimeout(function() {
          resolve(cur.name);
        }, 1 * 1000);

      }).then( function( val ) {
        
        // once loaded 
        log('loaded : ' + val);
        return val;
      
      });

      return pro;

    }
  };
});


files.reduce(function(t, v) {

  t.promise = t.promise.then(function(){ 
      return v.load();
  });
  
  return t;
}, {
  promise: Promise.resolve(1)
});

Использование async.eachSeries на массивах или async.forEachOfSeries на объектах.

Зацикливание объекта

var async = require('async');
var filesObject = {'file/path/1': {}, 'file/path/2': {}};

async.forEachOfSeries(filesObject, copyFileFromObj, allDone);

function copyFileFromObj(value, key, callback) {
  console.log('Copying file ' + key + '...');
  callback(); // when done
}

function allDone(err) {
  if (err) {
    console.error(err.message);
  }
  console.log('All done.');
}                

Зацикливание массива

var async = require('async');
var filesArray = ['file/path/1', 'file/path/2'];

async.eachSeries(filesArray, copyFile, allDone);

function copyFile(file, callback) {
  console.log('Copying file ' + file + '...');
  callback(); // when done
}

function allDone(err) {
  if (err) {
    console.error(err.message);
  }
    console.log('All done.');
}

Рабочий пример здесь: https://tonicdev.com/edinella/sync-loop-of-async-operations

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