我发现nodejs的异步是这样的。
请看图:
请看代码:
for(let i = 0; i < 5; i++){
test(i);
}
function test(i){
waterfall([
//步骤1
function(callback) {
thread(function (){
dlog('线程A'+i);
callback(null, i);
}, 800);
},
//步骤2
function(a, callback){
thread(function (){
dlog('线程B'+i);
callback(null, i);
}, 800);
}
], function (err, result) {
dlog('任务结束'+result);
});
}
function dlog(msg){
console.log(msg);
}
function thread(callBack, time){
setTimeout(function(){
callBack();
}, time)
}
也就是说,先执行完外层的任务,再执行内层的任务。
也就是顺序是:
A0,A1,A2,……
B0,B1,B2,……
现在我希望的顺序是:
A0,B0,A1,B1……
有什么办法吗?
办法可能就是执行完一个任务再执行下一个任务,不过这样速度可就慢了,体现不出异步执行的优点了
test(0);
function test(i){
waterfall([
//步骤1
function(callback) {
thread(function (){
dlog('线程A'+i);
callback(null, i);
}, 800);
},
//步骤2
function(a, callback){
thread(function (){
dlog('线程B'+i);
callback(null, i);
}, 800);
}
], function (err, result) {
dlog('任务结束'+result);
if(i < 5) test(i+1);
});
}
为啥我会有这么个需求。简单来说,假如你用程序读取1万个文件,只是打个比分。
你需要每次读取完成一个文件,告诉用户执行到26%这样的进度条给他。让他知道程序是在干活的。
如果按照平时的异步运行顺序。nodejs是循环读取了一万次之后,才开始读取第一个文件成功。那么循环一万次的这个过程中,进度条就是0%。毫无变化。让用户觉得,程序啥事也没干。
你这个问题用es6的Generator正合适,异步操作中可以yield终止当前操作,并交出控制权,然后进行下一轮操作,
或者模拟一下koa和express中间件的思路。加一个next(),终止当前操作,进行下一个任务
看了题目许久才大概明白题意,提问是项技术和艺术活。
简单修正下题主的问题,如果理解的仍然不正确还望题主再补充一下题目的细节并再精简下代码。
以下代码,众所周知肯定是乱序输出:
var asyncFunc = function (callback) {
setTimeout(function () {
callback();
}, ~~(Math.random() * 1000));
};
asyncFunc(function () {
console.log('parent - 1');
asyncFunc(function () {
console.log('child - 1');
});
});
asyncFunc(function () {
console.log('parent - 2');
asyncFunc(function () {
console.log('child - 2');
});
})
现在题主想让它先输出外层,再输出内层,输出结果是这样:
'parent - 1'
'parent - 2'
'child - 1'
'child - 2'
简单来说,题意是:外层是一个异步队列,要在外层的队列完成了之后内层的队列执行
下面针对这个题目作回答:
可以把所有的异步布置成Promise,并采用[Promise.all](http://es6.ruanyifeng.com/#docs/promise#Promise-all)
API来统一多个异步任务:继续用我后来提供的代码进行Promise包装:
var asyncFunc = function () {
//进行Promise包装,返回Promise
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve();
}, ~~(Math.random() * 1000));
});
};
//Promise.all传递一组Promise,并返回一个Promise,当所有的Promise返回了之后这个Promise异步任务才完成
Promise.all([
//把外层任务进行封装
asyncFunc().then(function () {
console.log('parent - 1');
}),
asyncFunc().then(function () {
console.log('parent - 2');
})
]).then(function () {
//内层任务
return Promise.all([
asyncFunc().then(function () {
console.log('child - 1');
return 'child-1';//传递内层的值
}),
asyncFunc().then(function () {
console.log('child - 2');
return 'child-2'
})
]);
}).then(function (datas) {
console.log(datas[0]);//child-1
console.log(datas[0]);//child-2
});
/*
输出:
"child -1"
"child -2"
*/
上面的代码把异步任务通过Promise
包装,然后使用Promise.all
分别把外层任务封装到一层,内层任务封装到一层。
由于Promise
是ECMAScript 2015(ES6)
才有的对象,现代浏览器支持程度并不够,所以我编写了一个Promise polyfill
的库,覆盖原生Promise 95% 以上的API特性和细节,参见我的github:https://github.com/linkFly6/Promise
当然如果题主不愿意把外层任务和内层任务进行隔离,那么可能需要自己针对自己的业务维护一套队列。
建议换一种思路。
================== 后题主追问告诉我理解错了题意 ========================
题主希望的输出的是:
'parent - 1'
'child - 1'
'parent - 2'
'child - 2'
如果要输出上面的结果,除了在上一个异步任务结束之后进行下一个异步任务这种方式实现之外,可能需要题主自行编写代码维护一个异步队列
。
不知道题主面对的是不是多异步并发,维护异步队列
这种效果,目前我写过类似这种效果:deferJSONP。
题主原话:
为啥我会有这么个需求。简单来说,假如你用程序读取1万个文件,只是打个比方。
你需要每次读取完成一个文件,告诉用户执行到26%这样的进度条给他。让他知道程序是在干活的。
如果按照平时的异步运行顺序。nodejs是循环读取了一万次之后,才开始读取第一个文件成功。那么循环一万次的这 个过程中,进度条就是0%。毫无变化。让用户觉得,程序啥事也没干。
node 是单线程异步