A E
/|\ |
B | C F
\|/
D
This is dependency graph. Every letter is async task. Every line is dependency - from bottom to top. So D can be evaluated only after A, B and C.
Task complete only after all subtasks evaluated.
It's important to start tasks in parallel and as soon as possible.
Most popular approach is callbacks (not for this task but overall):
function (done) {
var subtasksComplete = 0;
var _d;
AEval(function (err, a) {
if (err) {
return done(err);
}
var dDeps = {};
function dDepsFullfiled() {
DEval(a, dDeps.b, dDeps.c, function (err, d) {
if (err) {
return done(err);
}
_d = d;
subtasksComplete++;
if (subtasksComplete === 2) {
done(null, d);
}
});
}
BEval(a, function (err, b) {
if (err) {
return done(err);
}
dDeps.b = b;
if (dDeps.c) {
dDepsFullfiled();
}
});
CEval(a, function (err, c) {
if (err) {
return done(err);
}
dDeps.c = c;
if (dDeps.b) {
dDepsFullfiled();
}
});
});
EEval(function (err, e) {
if (err) {
return done(err);
}
FEval(e, function (err, f) {
if (err) {
return done(err);
}
subtasksComplete++;
if (subtasksComplete === 2) {
done(null, _d);
}
});
});
}
Oh my callback hell! So shitty. If you can do it better - don't hesitate, post me please. But anyway unlikely it will be much better.
Sure you know about async.js
function (done) {
async.auto({
a: function (done) {
AEval(done);
},
b: ['a', function (done, results) {
BEval(results.a, done);
}],
c: ['a', function (done, results) {
CEval(results.a, done);
}],
d: ['a', 'b', 'c', function (done, results) {
DEval(results.a, results.b, results.c, done);
}],
e: function (done) {
EEval(done);
},
f: ['e', function (done, results) {
FEval(results.e, done);
}]
}, function (err, results) {
done(err, results.d);
});
}
Way better. Now it's actually possible to read the code and understand developer's intent.
You still have some problems related to callbacks, e.g. calling it twice.
But let's try Promise
.
function () {
var dP = AEval()
.then(function (a) {
var bP = BEval(a);
var cP = CEval(a);
return [a, bP, cP];
})
.spread(DEval)
var fP = EEval()
.then(FEval);
return Promise.join(dP, fP, function (d) {
return d;
});
}
It isn't standart Promise
but bluebird
.
async.js
also isn't standart library, so in comparison there is no difference.
It's most common way to work with promises. I've seen this in some libs and projects.
It describes threads of async tasks nicely, so 'after A you can evaluate b and c' and so on.
But there is another one approach for Promises. Let's compare
function () {
var aP = aEval();
var bP = aP.then(bEval);
var cP = aP.then(cEval);
var dP = Promise.join(aP, bP, cP, DEval);
var eP = EEval();
var fP = eP.then(FEval);
return Promise.join(dP, fP, function (d, f) {
return d;
});
}
Oh wow! So plain. Looks almost like syncronious code.
Sync... oh I heard about 'async looks like sync'.
co
- your turn:
co(function* () {
var a = yield aEval();
var b = yield bEval(a);
var c = yield cEval(a);
var d = yield dEval(a, b, c);
var e = yield eEval();
var f = yield fEval(e);
return d;
});
Just compare with first callback-based version! There is no better solution in the world, isn't it?
Ok, I hope I didn't catch you.
This so nice looking code doesn't runs in parallel (eEval
runs only after dEval
).
It means that first version with callbacks in fact is better.
Here is the fixed version:
co(function* () {
var dP = co(function* () {
var a = yield aEval();
var bP = bEval(a);
var cP = cEval(a);
var b = yield bP;
var c = yield cP;
return yield dEval(a, b, c);
});
var fP = co(function* () {
var e = yield eEval();
return yield fEval(e);
});
yield fP;
return yield dP;
});
Not sure whether it possible to make it better. You know, help me if you can.
Hmm, but it doesn't look so nice as previous.
Some of you have questions:
"This stars* and yield
s - what does it mean?"
"Can I use it in browser?"
You have some troubles here.
In fact it's magic. I doubt that generators was created for this.
callback - hell
async - ok, but verbose, and it still callbacks
promise chains - nice for simple chains, but tricky for complex graphs
promise variables - best for complex graphs. Ok for simple
co - for simple async chain looks really nice. But also tricky for complex. And you have troubles with browsers
I think Promises is the best way to describe complex async graphs.
Promises were created for that.
You can use it in node or browser without building step.
For simple chains you can use chains of then
.
In comparsion with co
it will looks almost the same.
For complex async graphs it's better to create variable for each promise.
Please, ask questions, send corrections, suggest fixes.
Really want do discuss it.