Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrapping fs.readFile in a generator/yield

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.

like image 591
balupton Avatar asked Feb 19 '14 20:02

balupton


People also ask

What is yield in generator?

The yield keyword pauses generator function execution and the value of the expression following the yield keyword is returned to the generator's caller. It can be thought of as a generator-based version of the return keyword. yield can only be called directly from the generator function that contains it.

What is the purpose of the yield operator in the generator functions?

The yield keyword allows you to pause and resume a generator function ( function* ). In this syntax: The expression specifies the value to return from a generator function via the iteration protocol. If you omit the expression , the yield returns undefined .

What does the readFile () method require?

readFile() Method. Parameters: The method accept three parameters as mentioned above and described below: filename: It holds the name of the file to read or the entire path if stored at other location. encoding: It holds the encoding of file.

What fs read returns?

Returns the contents of the file named filename. If encoding is specified then this function returns a string. Otherwise it returns a buffer.


3 Answers

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

like image 174
lgersman Avatar answered Oct 19 '22 12:10

lgersman


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!

like image 20
Ebrahim Byagowi Avatar answered Oct 19 '22 12:10

Ebrahim Byagowi


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);
like image 24
Lucio M. Tato Avatar answered Oct 19 '22 11:10

Lucio M. Tato