I've read over several examples of code using JavaScript generators such as this one. The simplest generator-using block I can come up with is something like:
function read(path) {
return function (done) {
fs.readFile(path, "file", done);
}
}
co(function *() {
console.log( yield read("file") );
})();
This does indeed print out the contents of file
, but my hangup is where done
is called. Seemingly, yield is syntactic sugar for wrapping what it returns to in a callback and assigning the result value appropriately (and at least in the case of co
, throwing the error argument to the callback). Is my understanding of the syntax correct?
What does done
look like when yield
is used?
It returns only a single value to the caller, and the code execution stops as soon as it reaches the return statement. When a caller calls the generator function, the first yield is executed, and the function stops. It then returns the generator object to the caller where the value is stored.
If we pause the yield expression, the generator function will also get paused and resumes only when we call the next() method. When the next() method is encountered the function keeps on working until it faces another yield or returns expression.
yield keyword is used to create a generator function. A type of function that is memory efficient and can be used like an iterator object. In layman terms, the yield keyword will turn any expression that is given with it into a generator object and return it to the caller.
In JavaScript, yield is used to pause the execution of a function. When the function is invoked again, the execution continues from the last yield statement. A generator returns a generator object, which is an iterator. This object generates one value at a time and then pauses execution.
Seemingly, yield is syntactic sugar for wrapping what it returns to in a callback and assigning the result value appropriately (and at least in the case of co, throwing the error argument to the callback)
No, yield
is no syntactic sugar. It's the core syntax element of generators. When that generator is instantiated, you can run it (by calling .next()
on it), and that will return the value that was return
ed or yield
ed. When the generator was yield
ed, you can continue it later by calling .next()
again. The arguments to next
will be the value that the yield
expresion returns inside the generator.
Only in case of co
, those async callback things (and other things) are handled "appropriately" for what you would consider natural in an async control flow library.
What does done look like when yield is used?
The thread
function example from the article that you read gives you a good impression of this:
function thread(fn) {
var gen = fn();
function next(err, res) {
var ret = gen.next(res);
if (ret.done) return;
ret.value(next);
}
next();
}
In your code, yield
does yield the value of the expression read("file")
from the generator when it is ran. This becomes the ret.val
, the result of gen.next()
. To this, the next
function is passed - a callback that will continue the generator with the res
ult that was passed to it. In your generator code, it looks as if the yield
expression returned this value.
An "unrolled" version of what happens could be written like this:
function fn*() {
console.log( yield function (done) {
fs.readFile("filepath", "file", done);
} );
}
var gen = fn();
var ret1 = gen.next();
var callasync = ret1.value;
callasync(function next(err, res) {
var ret2 = gen.next(res); // this now does log the value
ret2.done; // true now
});
I posted a detailed explanation of how generators work here.
In a simplified form, your code might look like this without co
(untested):
function workAsync(fileName)
{
// async logic
var worker = (function* () {
function read(path) {
return function (done) {
fs.readFile(path, "file", done);
}
}
console.log(yield read(fileName));
})();
// driver
function nextStep(err, result) {
try {
var item = err?
worker.throw(err):
worker.next(result);
if (item.done)
return;
item.value(nextStep);
}
catch(ex) {
console.log(ex.message);
return;
}
}
// first step
nextStep();
}
workAsync("file");
The driver part of workAsync
asynchronously iterates through the generator object, by calling nextStep()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With