代码如下 :
async.mapLimit(items, 10, function(item, callback){
taskStart(item, function(err, data){ // 发起耗时任务
if(err){
callback();
}else{
callback(null, data);
}
});
}, function(err, resul){
result = result.filter(n => n);
// ...
});
**问题 : ** 有没有简单的办法,设置任务时间上限,超时后直接进入结果处理。
本楼正解
Promise.race() Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.race([p1, p2, p3]); 上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
下面是一个例子,如果指定时间内没有获得结果,就将Promise的状态变为reject,否则变为resolve。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p.then(response => console.log(response));
p.catch(error => console.log(error));
上面代码中,如果5秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。
可以, 使用 promise.timeout https://cnodejs.org/topic/574f88811176fe255bb8d7e3
支持资源清理, 像发起一个 http 请求, 在 onCancel 里写上 req.abort
之后, 如果不手动 abort, 请求还是会继续
本质上就是 Promise.race, 但是多了资源清理
下载文件示例见 https://github.com/magicdawn/yun-playlist-downloader/blob/v0.11.0/lib/index.js#L132-L135
fn(arg1, arg2, onCancel) -> Promise
使用 fn_withtimeout = ptimeout(fn, timeout, true)
最后一个 true 标识会在 fn 执行的时候传一个 onCancel
@zengming00 合成的 Promise 如何设置并发数?
@zengming00 学习了 mark~,之前一直不知道promise.race 干毛用 = =
@zengming00 不知道你的“本楼正解”是自己写的还是楼主改的,感觉你的思路与楼主的示例代码有些不符。楼主示例代码给人的感觉是要运行一系列任务,如果所有都在规定时间内完成了,结果是这一系列任务结果的一个集合;如果其中有任务超时了,到达查实时间后不会卡住也会返回结果,结果为其中已经执行好的任务结果的一个集合。而你给出的 Promise.race 的解决方案,只要有一个任务成功执行就会 resolve,这样结果中不会包含其他任务的执行结果;在所有任务都执行超时时才会触发附加的超时的 reject。
以下是我的一个思路,如果理解不对也请谅解。
根据自己最冲动最初始的想法写代码,可以满足要求,但是略显繁杂,在所有需要 callback 的地方都需要判断。
'use strict';
const async = require('async');
const list = [1, 2, 3, 4, 5, 6];
async.mapLimit(list, 10, function (item, callback) {
let alreadyCallback = false;
setTimeout(function () {
if (!alreadyCallback) {
callback(new Error('manual set error'));
return alreadyCallback = true;
} else {
// do nothing
}
}, 3000);
// // 超时任务
// if (item === 3) {
// setTimeout(function () {
// if (!alreadyCallback) {
// callback(null, item + 200);
// return alreadyCallback = true;
// } else {
// // do nothing
// }
// }, 5000);
// } else {
if (!alreadyCallback) {
callback(null, item + 100);
return alreadyCallback = true;
} else {
// do nothing
}
// }
}, function (error, results) {
if (error) {
return console.log(error);
}
console.log(results);
});
程序员的常规思路,当遇到需要重复的繁杂代码时一定要想办法精简。
'use strict';
const async = require('async');
const list = [1, 2, 3, 4, 5, 6];
async.mapLimit(list, 10, function (item, callback) {
let alreadyCallback = false;
const _callback = function (error, result) {
if(!alreadyCallback) {
callback(error, result);
alreadyCallback = true
}
};
setTimeout(function () {
return _callback(new Error('manual set error'));
}, 3000);
// // 超时任务
// if (item === 3) {
// setTimeout(function () {
// return _callback(null, item + 200);
// }, 5000);
// } else {
return _callback(null, item + 100);
// }
}, function (error, results) {
if (error) {
return console.log(error);
}
console.log(results);
});
至于在有任务超时时是触发错误,还是忽略错误,返回一个标识的结果到结果集合中,然后再从结果集合中过滤掉超时标识,这个倒是可以根据需求自己修改代码了。
@DuanPengfei 先感谢@DuanPengfei 的启发。 我尝试过:
- 超时时主动调用
callback()
. - 进行包装
var wrapped = async.timeout(foo, 3000);
然后使用async.mapLimit(items, 10, wrapped ...);
但是,两种方法,会造成重复调用
callback()
,会抛出错误if (fn === null) throw new Error("Callback was already called.");
方法1 可以通过状态标记alreadyCallback
避免Error: Callback was already called.
. 方法2 即使避免重复调用callback()
,还是存在计时的 bug, 并发数小于任务总数时,单个任务处理时间不超时,并不能保证整体处理时间不超时。
最终实现代码
var callbacks = []; var timer = setTimeout(function(){ callbacks.some(function(obj){ if(!obj.hasCalled){ obj.fn(new Error('TIME-OUT')); obj.hasCalled = true; return true; } return false; }); }, 10000); var foo = function(item, callback){ var obj = {'hasCalled':false, 'fn':callback}; callbacks.push(obj); var _callback = function(err, result){ if(!obj.hasCalled){ callback(err, result); obj.hasCalled = true; } } taskStart(item, function(err, data){ // 发起耗时任务 if(err){ _callback(); }else{ _callback(null, data); } }); }; async.mapLimit(items, 10, foo, function(err, resul){ clearTimeout(timer); if(err){ console.log(err.message); } result = result.filter(n => n); // ... });
@W-v-W 恩恩,我没有考虑到每次并发耗时累计起来的总时间问题,最终代码这样的实现就能达到需求了 😊