如何按顺序正确调用异步函数列表?
How can I properly call a list of async functions in order?
我正在尝试在 Node.js 中编写一个类似于 "robocopy /mir" 的函数,但似乎无法理解如何按顺序正确执行多个异步函数。
一些背景:
- 脚本在 Windows 上 运行,因此,我需要找到一些方法来复制文件,同时保留修改时间并接收进度通知。
- 为了解决这个问题,我继续在 .NET 中编写了我的复制函数(用 Edge.js 调用它)——这个复制函数只是回调到报告文件复制进度的节点函数。这件作品完美无缺。
要按顺序复制文件,我的第一个想法是执行如下操作:
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%
我似乎需要一种方法来将要完成的工作排队,然后再一次将其全部启动,每个项目在下一个继续之前完成。当所有项目都完成后,我需要得到通知。该解决方案看起来很简单,但由于我尝试的每个角度都伴随着另一个问题,所以我仍然无法理解。任何帮助将不胜感激,谢谢!
编辑: 正如我在评论中所述,Bergi 提供的答案是利用一个 确实 事实上 return 的函数一个承诺,而我的 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());
这可能不是最好的方法,但对我有用。
也许类似的东西可以解决问题:
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
我正在尝试在 Node.js 中编写一个类似于 "robocopy /mir" 的函数,但似乎无法理解如何按顺序正确执行多个异步函数。
一些背景:
- 脚本在 Windows 上 运行,因此,我需要找到一些方法来复制文件,同时保留修改时间并接收进度通知。
- 为了解决这个问题,我继续在 .NET 中编写了我的复制函数(用 Edge.js 调用它)——这个复制函数只是回调到报告文件复制进度的节点函数。这件作品完美无缺。
要按顺序复制文件,我的第一个想法是执行如下操作:
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%
我似乎需要一种方法来将要完成的工作排队,然后再一次将其全部启动,每个项目在下一个继续之前完成。当所有项目都完成后,我需要得到通知。该解决方案看起来很简单,但由于我尝试的每个角度都伴随着另一个问题,所以我仍然无法理解。任何帮助将不胜感激,谢谢!
编辑: 正如我在评论中所述,Bergi 提供的答案是利用一个 确实 事实上 return 的函数一个承诺,而我的 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());
这可能不是最好的方法,但对我有用。
也许类似的东西可以解决问题:
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