Giter Club home page Giter Club logo

gen-run's Introduction

gen-run

Generator Async Runner. Makes it possible to yield and wait for callbacks and continuables.

Supports both async functions that use node-style callbacks and continuable-returning async functions.

What is a Continuable?

A "continuable" is simply a function that accepts a node style callback as it's only argument.

A simple example is turning the setTimeout function to return a continuable instead of accepting a callback.

function sleep(ms) {
  return function (callback) {
    setTimeout(callback, ms);
  };
}

Using run()

Here is an example using ES6 generators with the gen-run library to implement sleep.

var run = require('gen-run');

run(function* () {
  console.log("Hello");
  yield sleep(1000);
  console.log("World");
});

If you pass a second parameter to run() it will call the function back with the return result of the function. For example,

run(function *() {
  console.log("Hello");
  yield sleep(1000);
  console.log("World");
  return "Hello World";
}, function (err, value) {
  if (err) console.log("Error encountered: " + err);
  else console.log(value);
});

Delegating Yield

Since this works by yielding continuables to the run function, delegating yield will also just work.

function* sub(n) {
  while (n) {
    console.log(n--);
    yield sleep(10);
  }
}

run(function* () {
  console.log("Start");
  yield* sub(10);
  console.log("End");
});

Synchronous Callback Protection

The runner is aware that sometimes callbacks may be called before the continuable function returns and has safeguards internally to prevent stack overflows.

function decrement(n) {
  return function (callback) {
    callback(null, n - 1);
  };
}

run(function* () {
  var i = 100000;
  while (i) {
    i = yield decrement(i);
  }
});

Multi-Callback Protection

There is also protection from evil functions that may call the callback multiple times when they should only ever call it once. Run will simply ignore subsequent calls from the same continuable.

function evil() {
  return function (callback) {
    callback(null, 1);
    setTimeout(function () {
      callback(null, 2);
    }, 100);
  }
}

run(function* () {
  console.log("Hello");
  yield evil();
  yield sleep(1000);
  console.log("World");
});

Error Handling

I assume the callback in the continuable will be of the form (err, item) and will return the item as the result of yield or throw err into the generator. This means that you can use async functions as if they were normal sync functions.

// Wrap fs.readFile as a continuable style function.
// Also intercept ENOENT errors and instead return undefined for the result
function readFile(path, encoding) {
  return function (callback) {
    fs.readFile(path, encoding, function (err, data) {
      if (err) {
        if (err.code === "ENOENT") return callback();
        return callback(err);
      }
      return callback(null, data);
    });
  };
}

run(function* () {
  try {
    var contents = yield readFile("/myfile.txt", "utf8");
    if (contents) {
      // the file existed and was not empty
    }
  }
  catch (err) {
    // There was a problem reading the file, but it did exist.
  }
});

What about Parallel Work?

Gen-Run has basic built-in support for parallel work via yielding arrays or maps of continuables.

If you yield an array of continuable functions, it will run them all in parallel and return the results. If there is an error in any of them, the expression will throw.

The same works for objects except the result is an object. This allows for named data.

But ES6 isn't everywhere yet.

That's OK. In node.js land, you can require your users or your app's deployment to use a certain version of node.js that has generators. But for browsers and older versions of node there are transpilers. https://github.com/google/traceur-compiler, for example can convert ES6 generator code to vanilla ES5 code.

Do I have to wrap everything I use then?

No, gen-run also works with any callback based API using a slightly more explicit syntax.

run(function* (gen) {
  console.log("Hello");
  yield setTimeout(gen(), 1000);
  console.log("World");
});

This alternate API can be mixed and matched with continuable style APIs. If you yield a function, it will assume it's a continuable and pass in a callback. If you don't, it's your responsibility to pass in the generated callback manually in the right place.

If you want to use delegate yield with the explicit style it's up to you to pass the gen function to the child generator.

function* sub(gen, n) {
  while (n) {
    console.log(n--);
    yield setTimeout(gen(), 10);
  }
}

run(function* (gen) {
  console.log("Start");
  yield* sub(gen, 10);
  console.log("End");
});

Credits

This library is the result of my lua research and researching many other similair libraries and taking the parts I like from them. Libraries I took inspiration from are:

License

The MIT License (MIT)

Copyright (c) 2013 Tim Caswell

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

gen-run's People

Contributors

chuckjaz avatar creationix avatar jmar777 avatar tomibelan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

gen-run's Issues

Generators stop after a caught exception

Consider the following program:

var run = require('gen-run');

function tick(callback) {
    process.nextTick(function() {
        callback();
    });
}

function error(callback) {
    callback(new Error());
}

function *throws() {
    yield tick;
    yield error;
}

function *catches() {
    try {
        console.log('before throws')
        yield *throws();
        console.log('after throws')
    }
    catch (err) {
        console.log('in catch')
    }
    yield tick;
    return 'Something';
}

function *tests() {
    var a = yield *catches();
    console.log('catches returned: ' + a)
}

run(tests);

It should display:

before throws
in catch
catches returned: Something

but the last line is not printed because the generator stops after the throw. I suspect it is because of the if (err) return iterator.throw(err); unconditionally stops iteration. If the generator has a catch throw() acts like next().

Note that the yield tick; in catches() is important as it forces the generator to require an next() call after a throw() for correct operation.

Support AGen specification for asynchronous generators

To generate discussion, feedback and achieve interoperability:

You know the details, but for anyone else coming here...

@Raynos and I put together the AGen specification with your feedback for asynchronous generators, generators + asynchrony (e.g., promises, continuables, etc...) which we're using in our own libraries (gens) to create a cohesive specification for asynchronous generator interoperability.

Some highlights:

  • Asynchrony agnostic: implementor/user could support promises, thunks, or something new and exotic, but must support yielding on generator objects.
  • Support for serial (vanilla yield) or parallel (yield [...]) execution.
  • Separation of Errors and Exceptions

Node v0.11.3 syntax error

I have tried running the following example in node v0.11.3 with the flags --harmony --use-strict (prompted by this tweet), but I get a syntax error Unexpected token *. What does "full support for harmony generators" really mean?

var run = require('gen-run');

run(function* () {
  console.log("Hello");
  yield sleep(1000);
  console.log("World");
});

A lot of boilerplate code

Hi,

Thanks for sharing this code, really enjoying the simplistic way of yielding here.

I look to be writing the following a lot and I wanted to check that I wasn't missing something before I went ahead and created a method to make this easier.

Class.prototype.method = function(some, arguments) {
    const self = this;  

    return function(callback) {
        run(function* (genCallback) {
            //Code with yielding inside

             callback(null, obj);
        });
    });
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.