10

I'm trying to get my head around generators and yield in JavaScript and Node.js, but having an issue.

Ideally, what I'd want to do is wrap fs.readFile with generators/yield, so that I can use it synchronously without blocking anything.

I've come up with the following code:

function readFileSync (path) {
    return (function *(){
        return yield require('fs').readFile(path, function *(err, data){
            yield data;
        });
    })();
}

console.log(readFileSync('test-file.txt'));

But, unfortunately, readFileSync just always returns {} instead of the file content.

Hopefully what I want to achieve is still possible, or perhaps I've completely missed the point of generators/yield and I'm using it entirely incorrectly, in which case pointing out where I've gone wrong and any resources would be great.

balupton
  • 47,113
  • 32
  • 131
  • 182
  • In all generator examples I've seen were promises involved. On which docs/posts/howtos is your script based? – Bergi Feb 19 '14 at 21:09
  • Why not simply use `fs.readFileSync`? – Bergi Feb 19 '14 at 21:09
  • 1
    `Why not simply use fs.readFileSync?` It's blocking. The use case here is to make this line non-blocking: https://github.com/bevry/ssg-experiments/blob/aba629383d0945fffc843e2d43c4b180ca0083bd/index.js#L24 – balupton Feb 19 '14 at 21:11
  • `In all generator examples I've seen were promises involved. On which docs/posts/howtos is your script based?` no specific example as nothing was quite what I was after, so this is just a mashup of my best guess... – balupton Feb 19 '14 at 21:13
  • 1
    "Sync" means "blocking". I don't think this is possible, check out [How to encapsulate async function calls into a sync function in Node.js or Javascript](http://stackoverflow.com/questions/21819858/how-to-encapsulate-async-function-calls-into-a-sync-function-in-node-js-or-javas) – Bergi Feb 19 '14 at 21:16

4 Answers4

10

How about using node with harmony features enabled (node --harmony) and this super simple ES6 snippet :

function run( gen, iter) {
  (iter=gen( (err, data) => (err && iter.raise(err)) || iter.next(data))).next();
}

run(function* (resume) {
    var contents = yield require('fs').readFile(path, resume);
    console.log(contents);
});

You can read more about this dead simple pattern (and try it out online) at this article at orangevolt.blogspot.com

lgersman
  • 2,178
  • 2
  • 19
  • 21
2

Just a bit desugerifying and update (seems raise is renamed to throw) lgersman's answer to make it work with io.js 1.0.4:

function run(gen) {
  var iter = gen(function (err, data) {
    if (err) { iter.throw(err); }
    return iter.next(data);
  });
  iter.next();
}

run(function* (resume) {
  var contents = yield require('fs').readFile(path, resume);
  console.log(contents);
});

Thank you lgersman!

Community
  • 1
  • 1
Ebrahim Byagowi
  • 10,338
  • 4
  • 70
  • 81
1

You can use a helper lib like Wait.for-ES6 (I'm the author)

Pro's: You can call sequentially any standard async node.js function

Con's: You can only do it inside a generator function*

Example using fs.readdir and fs.readfile (both are standard async node.js functions)

var wait=require('wait.for-es6'), fs=require('fs');

function* sequentialTask(){
   var list = yield wait.for(fs.readdir,'/home/lucio');
   console.log(list); // An array of files
   var data = yield wait.for(fs.readFile,list[0]); //read first file
   console.log(data); // contents
}

wait.launchFiber(sequentialTask);
Lucio M. Tato
  • 5,639
  • 2
  • 31
  • 30
-3

It is impossible to turn an asynchronous function into a synchronous function with generators.

Generators can interrupt themselves, but they can't interrupt control flow of other functions.

So the only way how your code can work is if it is inside another generator:

console.log(yield* readFileSync('test-file.txt'));
alex
  • 11,935
  • 3
  • 30
  • 42