Callback-free control flow for Node using ES6 generators.

Related tags

Control Flow suspend
Overview

suspend

Generator-based control-flow for Node enabling asynchronous code without callbacks, transpiling, or selling your soul.

Suspend is designed to work seamlessly with Node's callback conventions, promises, and thunks, but is also compatible with code that follows other conventions.

Related reading for the generator-uninitiated: What's the Big Deal with Generators?

Note: Generators are a new feature in ES6 and are still hidden behind the --harmony-generators (or the more general --harmony) flag in V8:

$ node --harmony-generators your-script.js

Quick Examples

Working with Node-style callbacks:

var suspend = require('suspend'),
    resume = suspend.resume;

suspend(function*() {
    var data = yield fs.readFile(__filename, 'utf8', resume());
    console.log(data);
})();

Working with promises:

var suspend = require('suspend');

suspend(function*() {
    var user = yield UserModel.find({ username: 'jmar777' });
    console.log(user.favoriteColor);
})();

Working with thunks:

var suspend = require('suspend'),
    readFile = require('thunkify')(require('fs').readFile);

suspend(function*() {
    var package = JSON.parse(yield readFile('package.json', 'utf8'));
    console.log(package.name);
});

Installation

$ npm install suspend

Documentation

API

suspend.async(fn*)

Accepts a generator function fn*, and returns a wrapper function that follows Node's callback conventions. Note that the wrapper function requires a callback as the last parameter.

Example:

var readJsonFile = suspend.async(function*(fileName) {
    var rawFile = yield fs.readFile(fileName, 'utf8', suspend.resume());
    return JSON.parse(rawFile);
});

// the resulting function behaves like any other async function in node
readJsonFile('package.json', function(err, packageData) {
    console.log(packageData.name); // 'suspend'
});

Note that .async() lets you return your final result, instead of having to explicitly accept a callback parameter and pass the result manually. Likewise, any uncaught errors will be passed to the callback as the error argument (see the section on error handling for more information).


suspend.promise(fn*)

Accepts a generator function fn*, and returns a wrapper function that returns a Promise. If a value is returned (or the generator function completes with no explicit return value), the promise is resolved. If an error is thrown, then the promise is rejected.

Example:

var getFavoriteNumberByUsername = suspend.promise(function*(username) {
    var user = yield UserModel.find({ username: username });
    return user.favoriteColor;
});

// the resulting function exposes a promise API:
var promise = getFavoriteNumberByUsername('jmar777');

Note that the above example also demonstrates the ability to yield promises, which is documented below.


suspend.fn(fn*)

Accepts a generator function fn*, and returns a wrapper function that, unlike .async(), makes no assumptions regarding callback conventions. This makes .fn() useful for event handlers, setTimeout() functions, and other use cases that don't expect Node's typical asynchronous method signature.

Note: As a shorthand convenience, suspend(fn*) is an alias for suspend.fn(fn*).

Example:

var listener = suspend(function*(req, res) {
    // wait 2 seconds
    yield setTimeout(suspend.resume(), 2000);
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Thanks for being patient!');
});

http.createServer(listener).listen(1337, '127.0.0.1');

suspend.run(fn*, [cb])

Accepts a generator function fn* and runs it immediately. If an optional callback cb is provided, any errors or return values will be passed to it.

Example:

suspend.run(function*() {
    var data = yield fs.readFile('file1', suspend.resume());
    yield fs.writeFile('file1-copy', data, suspend.resume());
}, function(err) {
    if (err) console.error(err);
});

suspend(fn*)

An alias for suspend.fn(fn*).

Suspending and Resuming Execution

yield

The yield keyword is a new language feature associated with generators in ES6. Whenever a yield is encountered, execution of the generator is "suspended" until something external tells it to resume.

In suspend, as the name implies, we use yield to suspend the generator while performing asynchronous operations, and then resume it once the operation is complete.

If you're using promises or thunks, suspend can resume for you automatically. However, given that the majority of the Node ecosystem relies on callbacks, suspend provides some simple mechanisms for interacting with callback-based code: resume() and resumeRaw().


suspend.resume()

A simple callback factory for interacting with Node-style asynchronous functions.

Example:

suspend(function*() {
    var data = yield fs.readFile(__filename, 'utf8', suspend.resume());
})();

As can be seen, resume() creates Node-style callbacks that know how to handle the results from the asynchronous operation and automatically resume the generator. If the first argument passed to the callback is an error (or any other truthy value), it will be thrown back in the generator body. Otherwise, the value of the second argument will be the result of the asynchronous operation.


suspend.resumeRaw()

While resume() knows how to intelligently handle Node-style callbacks, sometimes we have to work with code that doesn't follow these conventions. In these situations, resumeRaw() can be used. resumeRaw() makes no assumptions regarding what the arguments are, and simply provides the results back as an array.

Consider, for example, fs.exists(), a remnant from Node's early days and one of the few extant API methods that doesn't follow the error-first callback convention. While you shouldn't ever actually use fs.exists(), this is a good example of when you would use resumeRaw():

Example:

suspend(function*() {
    var data = yield fs.exists(__filename, suspend.resumeRaw());
    console.log(data); // [ true ]
})();

Promises

As was previously mentioned, suspend is also designed to play nice with promises. In fact, promises and suspend make a particularly nice combination, as it completely alleviates the need for callbacks (or resume()). To use suspend with promises, simply yield the promise itself.

Example:

suspend(function*() {
    var user = yield UserModel.find({ username: 'jmar777' });
})();

The above is an example of working with mongoose, which returns promises for asynchronous operations. If a yield expression evaluates to a "thenable", then suspend can figure out the rest.


Thunks

Thunks provide a nice, lightweight alternative to promises when working with generators. Sometimes referred to as continuables, thunks are simply functions returned from asynchronous operations, that accept a single node-style callback parameter. For creating "thunkified" versions of asynchronous functions, I recommend TJ Holowaychuk's thunkify module.

Example:

var readFile = thunkify(fs.readFile);

suspend(function*() {
    var data = yield readFile(__filename, 'utf8');
});

As can be seen, one must simply yield the thunk itself and suspend will appropriately handle the eventual result or error. Just like with promises, thunks and suspend make a particularly nice combination, as once again there's no need to pass in resume() or other callback mechanism into the async function itself.

Parallel Operations

While yielding is a convenient way to wait for an operation to complete, it does force things to be executed in series. Sometimes, however, we wish to do things in parallel. In suspend, parallel operations are made easy with fork() and join().

suspend.fork() and suspend.join()

Unlike resume(), which requires you to yield first, fork() creates a callback that will temporarily store the completion values until you subsequently yield on join(). This allows any arbitrary number of parallel operations to be "forked", without suspending execution until we are ready to use their results.

Example:

suspend(function*() {
    var fileNames = yield fs.readdir('test', suspend.resume());

    fileNames.forEach(function(fileName) {
        fs.readFile('test/' + fileName, 'utf8', suspend.fork());
    });

    var files = yield suspend.join();

    var numTests = files.reduce(function(cur, prev) {
        return cur + prev.match(/it\(/g).length;
    }, 0);

    console.log('There are %s tests', numTests);
})();

The order of the results array will be based on the order in which you called fork(), so there's no need to worry about which operation completes first.


Combining with Other Control-Flow Libraries

If fork() and join() aren't sufficient for your use case, keep in mind that suspend is happy to work with your existing control-flow libraries of choice, such as caolan/async.

Example:

suspend(function*() {
    var fileNames = yield fs.readdir(__dirname, suspend.resume()),
        stats = yield async.map(fileNames, fs.stat, suspend.resume());
    console.log(stats);
})();

Error Handling

Suspend allows us to handle both synchronous and asynchronous errors the same. Whenever an error is passed to the resume() callback, or a promise or thunk resolves to an error, that error is thrown at the site of the yield expression. This allows us to use native try/catches to handle errors, regardless of whether they occurred synchronously or asynchronously.

Example:

suspend.run(function*() {
    try {
        var rawFile = yield fs.readFile('package.json', 'utf8', suspend.resume()),
            packageData = JSON.parse(rawFile);

        console.log(packageData.license); // "MIT"
    } catch (err) {
        console.error('There was an error reading or parsing the file');
    }
});

While unifying the error handling model is convenient, it can also be tedious to write lots of try/catches. For this reason, both .run() and .async() will automatically pass any unhandled errors to their callbacks. This makes it trivial to write functions using suspend that safely handle errors in accordance with Node's callback conventions:

Example (.run()):

suspend.run(function*() {
    var rawFile = yield fs.readFile('package.json', 'utf8', suspend.resume()),
        packageData = JSON.parse(rawFile);
    console.log(packageData.license); // "MIT"
}, function(err) {
    if (err)  {
        console.error('There was an error reading or parsing the file');
    }
});

Example (.async()):

var readJsonFile = suspend.async(function*(fileName) {
    var rawFile = yield fs.readFile(fileName, 'utf8', suspend.resume());
    return JSON.parse(rawFile);
});

readJsonFile('package.json', function(err, packageData) {
    if (err) {
        console.error('There was an error reading or parsing the file');
    } else {
        console.log(packageData.license); // "MIT"
    }
});

Here's what's important to remember:

  1. If an error occurs, you'll have a chance to capture it with a try/catch.
  2. If you don't catch an error, and you're using suspend(), .async() or .run() with a callback, then the error will be passed to the callback.
  3. Otherwise, the unhandled error will be re-thrown globally.

Versioning and Stability

Please note that generators are currently only supported in unstable (v0.11.x) versions of Node, so it would probably be wise to treat suspend as no more stable than the version of Node that supports it. Also note that suspend follows SemVer for versioning, so breaking changes will never be introduced in a patch release.

Feedback is greatly appreciated, so if you find anything or have any suggestions, please open an issue, tweet at me, or shoot me an email ([email protected])!

Running Tests

$ npm test

Bonus: thanks to Ben Newman, the full suspend test suite can also be run in pre-v0.11.x versions of Node (using regenerator):

$ npm run-script test-es5

Credits

A special thanks to Gorgi Kosev for his consistently valuable feedback on suspend's API (and sundry related topics).

Additional thanks goes to Ben Newman, Willem, Michael Hart, Sonny Piers, and Yunsong Guo for their contributions to the project.

License

The MIT License (MIT)

Copyright (c) 2014 Jeremy Martin

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.

Comments
  • Send single result if `throw` and `flatten` options specified

    Send single result if `throw` and `flatten` options specified

    This covers 90% of callback cases in my experience.

    (I was gonna add an issue, but figured this was the easiest way to illustrate what I meant... if you like the idea I can add tests, etc)

    opened by mhart 11
  • Produce a callback-less coroutine returning a promise

    Produce a callback-less coroutine returning a promise

    It would be very handy to produce a coroutine from a wrapper instead of a one with a callback. This means instead of this:

    var foo = suspend.async(function*(param){ /*...*/ });
    yield foo('bar', suspend.resume());
    

    would like to work with a promise out of the box

    var foo = suspend.promise(function*(param){ /*...*/ });
    yield foo('bar');
    

    This may bring some overhead, but it's much easier when You work with promise-like APIs (eh webdriver) and need to check if function You're calling returns a promise or is a wrapped generator. Also, it's easier to explain to co-workers how to use yield for async.

    opened by TobiaszCudnik 9
  • Use regenerator to run tests in any version of Node.js

    Use regenerator to run tests in any version of Node.js

    The https://github.com/facebook/regenerator project transforms ES6 generator functions into ES5 that behaves the same way, so Node.js v0.11.2 is no longer a strict requirement for using https://github.com/jmar777/suspend.

    More explanation about regenerator can be found here and here.

    In Node.js v0.11.2 and above, the tests will still run natively using the --harmony-generators flag in addition to running in translation using regenerator.

    To be clear, this pull request does not impose regenerator on users of suspend who only need compatibility with Node.js v0.11.2, but it does demonstrate that it's possible to use suspend in any ES5-compatible environment.

    opened by benjamn 6
  • Generators cannot be resumed once completed.

    Generators cannot be resumed once completed.

    Hello, I'm using suspend.resume() to receive node cb style, but I'm obtain "Generators cannot be resumed once completed." error. Can u help me?

    suspend(function* () { var available = Modulus.checkModulus(company, 'advanced_config', suspend.resume()); if (available) { console.log('YEY');} })();

    In another file: var checkModulus = function (company, modulus, callback) { return callback(null, true); };

    Thanks in advance!

    opened by polizinha 4
  • avoid zalgo problem and prevent promise swallowing exception

    avoid zalgo problem and prevent promise swallowing exception

    with current implementation, the callback will be called immediately when the generator function is executed completely. For example,

    suspend.run(function* () {
        throw 'something' // or return 'something'
    }, function (err) {
        console.log('called first');
    });
    console.log('called later')
    

    It has following two problem:

    • zalgo problem (callback is sometimes sync, sometimes async)
    // callback will be called before `suspend.run` returns
    suspend.run(function* () {
        throw new Error();
    }, callback);
    
    // callback will be called async after `suspend.run` returns
    suspend.run(function* () {
        yield setTimeout(suspend.resume(), 10);
        throw new Error('');
    }, callback);
    
    // therefore, it will release zalgo problem.
    // about zalgo, please read following link
    // http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
    
    • promise will swallow exception
    // without callback
    suspend.run(function* () {
        yield Promise.reject(new Error()); // this error will be swallowed by the promise!
    });
    
    // with callback
    suspend.run(function* () {
        yield Promise.resolve('something');
    }, function () {
        throw new Error(); // this error will be swallowed by the promise!
    });
    
    // because `promise.then` is used to get value from the promise and
    // `promise.then` will create a new Promise. If there is exception within
    // generator function or callback function, the created promise will swallow
    // the exception, which is not what we expected. 
    

    A simple solution would be defer the callback or exception to next tick (use process.nextTick or setImmediate)

    opened by gyson 4
  • Global pollution

    Global pollution

    Wouldn't it be better to not pollute the global namespace with suspenderStack and instead place it in Suspender, i.e. Suspender. suspenderStack. Also, why can't you do the same for the other global functions, setActiveSuspender, getActiveSuspender, clearActiveSuspender, etc.

    opened by cscalfani 3
  • exclude folders from test files

    exclude folders from test files

    Continuation of #9 Tests broke on my machine because my IDE ( webstorm ) added a hidden folder to the test folder. Which then throws an error when using node 0.10.21 because run.js will attempt to regenerate this folder.

    I'm not sure if this is a fix you want but it worked for me.

    opened by helmus 3
  • sync detect

    sync detect

    continuation of #10

    I understand the need for the transparent resolution. Although i do think that sync callbacks are an edge case and ~~that handling them explicitly seems appropriate, even more so because of the increased code documentation then the neglectable performance gain.~~ I'm actually starting to think that this approach might in fact be more elegant than #10.

    Anyway, i followed up on your advice and implemented some setImmediate magic in order to detect if the call is synchronous. If the callback is asynchronous then resume is called immediately, if it is synchronous the arguments are stored on the instance and resume will be called from the setImmediate function call that was already started to check if the call is synchronous.

    I have no idea if this will cause anything to be faster than the current implementation but i do feel this might resolve async handlers more naturally.

    opened by helmus 3
  • Add some support for parallelization.

    Add some support for parallelization.

    Something like yielding an array of "resumables", or an object where the values are resumables. Perhaps some like caolan/async-style suspend.parallel(), etc. methods.

    opened by jmar777 3
  • Be able to yield normal functions.

    Be able to yield normal functions.

    This isn't a big deal. But I ran into this when refactoring a function to return a promise:

    
    function foo() {
        return 42;
    }
    
    function bar() {
    
        //... 
    
        return promise;
    }
    
    suspend.promise(function*() {
    
        yield foo();
    
        // control exits here ... somewhere at Suspender.prototype.handleYield()
    
        yield bar();
    })();
    

    It'll be convenient if yield wraps the return value of foo() with Promise.resolve().

    opened by dashed 2
  • make sync callbacks explicit

    make sync callbacks explicit

    I'm submitting this PR in response to 1bda954d0da1c508918cdaab59234bef299262b6 Suspend is now using 2 cycles to handle off async callbacks in favour of normalizing sync callbacks. I'm sure the performance impact of this is minimal but this doesn't feel like the right thing to do.

    Adding a sync resumator option to the resume callback fixes this by allowing to signal that the callback is synchronous. This also provides better code documentation, when reading through a suspend function a developer immediately notices if a callback is synchronous or asynchronous.

    You could argue that sometimes you don't know if a callback is going to be async or sync, this is really more an inconsistency in the library you are using. But this can of course be fixed by just using the sync resumator which will handle both cases fine.

    This PR will break synchronous promises, you can fix this like this:

    yield doStuffEventually.then(r.sync);
    

    I don't really know a better way to fix it at this moment. Any feedback is appreciated.

    opened by helmus 2
  • .async gone (-> README.md documentation)

    .async gone (-> README.md documentation)

    I'm just updating package dependencies of a project and it fails due to .async gone.

    From git history I assume it has just been renamed to .callback? Yes?

    Suggestion update README.md ;-) (Maybe with a hint for a while that it used to be called .async.)

    PS: I'm using this module since years for all my major node.js projects, thanks BTW.

    PPS: --harmony-generators seems doesn't need to be specified anymore with recent node.js builds (I just upgraded to v9.2. I don't know where ES6 started to become fully enabled by default)

    opened by axkibe 0
  • Problem in resumeRaw() ?

    Problem in resumeRaw() ?

    Suppose I use a library which has a function with unconventional callback argument structure, like: function myFunction(arg, cb) { cb('a', 'b', 'c', 'd', 'e'); }

    when I use resumeRaw like this: suspend(function*() { console.log(yield myFunction(0, suspend.resumeRaw())); })();

    I get: [ 'a', 'b' ]

    I modified suspend.js a little bit, to this:

    /**
     * Resumes execution of the generator once an async operation has completed.
     */
    Suspender.prototype.resume = function resume(err, result) {
        // if we have been synchronously resumed, then wait for the next turn on
        // the event loop (avoids 'Generator already running' errors).
        if (this.syncResume) {
            return setImmediate(this.resume.bind(this, ...arguments));
            // return setImmediate(this.resume.bind(this, err, result));
        }
    
        if (this.rawResume) {
            this.rawResume = false;
            this.nextOrThrow(Array.prototype.slice.call(arguments));
        } else {
            if (this.done) {
                throw new Error('Generators cannot be resumed once completed.');
            }
    
            if (err) return this.nextOrThrow(err, true);
    
            this.nextOrThrow(result);
        }
    };
    

    and got this (my intended result): [ 'a', 'b', 'c', 'd', 'e' ]

    Did I do something wrong or it's a bug?

    opened by seantheyahn 0
  • Error when using npm@3

    Error when using npm@3

    The changes in npm v3 (specifically, flattening the node_modules folder) cause the library to bomb out at a filepath-based require for the 'promise' npm package.

    Error: Cannot find module '../node_modules/promise/lib/es6-extensions'
        ...
        at Object.<anonymous> (...\node_modules\suspend\lib\suspend.js:1:77)
    
    opened by jon-hall 1
  • Just a fake, may have another better way

    Just a fake, may have another better way

    eg:

        this.sleep = function(ms, cb){
            setTimeout(cb, ms)
        }
        suspend.run(function*(resume) {
            yield this.sleep(1000, resume())
        }, function(err, ret) {
            return next(err, ret)
        }, this)
    
    opened by skyblue 0
Owner
Jeremy Martin
Writing Code @ Elemental Cognition | Full-time Programmer | Part-time Ranter | Traveler | Photographer | Born Again | Husband | Father
Jeremy Martin
An async control-flow library that makes stepping through logic easy.

Step A simple control-flow library for node.JS that makes parallel execution, serial execution, and error handling painless. How to install Simply cop

Tim Caswell 2.2k Dec 22, 2022
:surfer: Asynchronous flow control with a functional taste to it

Asynchronous flow control with a functional taste to it λ aims to stay small and simple, while powerful. Inspired by async and lodash. Methods are imp

Nicolás Bevacqua 763 Jan 4, 2023
The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc)

co Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way. Co v4 co@4

TJ Holowaychuk 11.8k Jan 2, 2023
Promisify a callback-style function

pify Promisify a callback-style function Install $ npm install pify Usage const fs = require('fs'); const pify = require('pify'); (async () => { //

Sindre Sorhus 1.5k Jan 4, 2023
Async utilities for node and the browser

Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript. Although originally designed f

Caolan McMahon 27.8k Dec 31, 2022
Clock and task scheduler for node.js applications, providing extensive control of time and callback scheduling in prod and test code

#zeit A node.js clock and scheduler, intended to take place of the global V8 object for manipulation of time and task scheduling which would be handle

David Denton 12 Dec 21, 2021
Flow control and error handling for Node.js

NOTE: This project is deprecated and no longer being actively developed or maintained. See Issue #50 for details. StrongLoop zone library Overview The

StrongLoop and IBM API Connect 280 Feb 18, 2022
Clock and task scheduler for node.js applications, providing extensive control of time and callback scheduling in prod and test code

#zeit A node.js clock and scheduler, intended to take place of the global V8 object for manipulation of time and task scheduling which would be handle

David Denton 12 Dec 21, 2021
An async control-flow library that makes stepping through logic easy.

Step A simple control-flow library for node.JS that makes parallel execution, serial execution, and error handling painless. How to install Simply cop

Tim Caswell 2.2k Dec 22, 2022
:surfer: Asynchronous flow control with a functional taste to it

Asynchronous flow control with a functional taste to it λ aims to stay small and simple, while powerful. Inspired by async and lodash. Methods are imp

Nicolás Bevacqua 763 Jan 4, 2023
The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc)

co Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way. Co v4 co@4

TJ Holowaychuk 11.8k Jan 2, 2023
A enhanced web storage with env support, expire time control, change callback and LRU storage clear strategy.

enhanced-web-storage A enhanced web storage with env support, expire time control, change callback and LRU storage clear strategy. How to Start import

Ziwen Mei 15 Sep 10, 2021
A three.js and roslibjs powered web-control for zju fast-drone-250 for laptop-free flight control

Web Control for ZJU Fast-Drone-250 A three.js and roslibjs powered web-control for zju fast-drone-250 for laptop-free flight control (tested on Xiaomi

null 6 Nov 11, 2022
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
Web based application that uses playerctl in it backend to control remotely your audio using the frontend as remote control.

Linux Remote This is a web based application that uses playerctl in it backend to control remotely your audio using the frontend as remote control. Do

Gabriel Guerra 4 Jul 6, 2022
Awesome Books project with ES6 is an application that was built using Vanilla JavaScript with ES6 features like using arrow functions. This application allows you to keep records of your favorite books.

Javascript Project Awesome Books with ES6 Using Javascript to create a simple Awesome Books project. Populating the books list and then removing one b

Ghazanfar Ali 8 Sep 28, 2022
Write JSX-driven components with functions, promises and generators.

Crank.js Write JSX-driven components with functions, promises and generators. Documentation is available at crank.js.org. Crank.js is in a beta phase,

null 2.5k Jan 1, 2023
JSON-Schema + fake data generators

Use JSON Schema along with fake generators to provide consistent and meaningful fake data for your system. What's next? Breaking-changes towards v0.5.

JSON Schema Faker 2.9k Jan 4, 2023