Setlist will sequential-ish your asynchronous code with ES6 Generator Function and Promise - Say goodbye to indentation hell.
- Setup
- Getting Started
- Setlist Chain
- Wrapping Callback Function
- Transform Generator Function Class with Promise
- Create Callback Handler with Generator Function
- Testing
- Bugs
- License
Install it with npm
.
npm install --save setlist
Then use them in your project by requiring setlist
.
const run = require('setlist');
This topics requires you to understand the basics of Promise https://www.promisejs.org
This is an example of function using Promises to handle some asynchronous operation.
// Suppose that getDataFromDb is returning promises
getDataFromDb('myTable', name)
.then(function(data) {
// We got the data, but the next function is callback only
// so we have to wrap it up with promises
return new Promise(function(resolve) {
getPeopleWithCallback(data, resolve);
});
})
.then(function(people) {
// We now get the people, now let's decide
if ('good' in people) {
return 'yeay';
}
// Lets clean up things first
else {
// Suppose that this function is returning promises
return cleanUpPeople(people).then(function() {
return 'yeay after clean';
});
}
});
Meh. Let's write that again in Setlist.
function* yeayFunction(name) {
// Get the data from Db (promise function)
let data = yield getDataFromDb('myTable', name);
// Get people with callback function
// Setlist only accepts Promises, so we should promisify it with
// List.async() function
let people = yield List.async(getPeopleWithCallback)(data);
// Lets decide
if ('good' in people) {
// Yeay it's good
return 'yeay';
}
// You should cleaned up first
else {
yield cleanUpPeople(people);
// Now it is okay to return
return 'yeay after clean';
}
}
RULE OF THUMB
There is always ayield
keyword before calling asynchronous function and generator function.
Do not useyield
when working with synchronous function.
Then, run the yeayFunction()
with the run()
function.
run(yeayFunction('Joe'));
Done. No more indentation hell, and of course, no callback hells.
You can chain multiple generator function execution with run(...).next()
.
// Chain multiple execution with .next()
run(yeayFunction('Joe'))
.next(anotherFunction())
.next(lastFunction());
Or, if you prefer creating new generator function, you can also call them with yield keyword. The yield keyword also pass the return value of child generator function to the parent.
// Or collect them in new generator function
function* taskList() {
let status = yield yeayFunction('Joe');
yield processFunction(status);
yield lastFunction();
}
// Execute the parent generator function
run(taskList());
Setlist does not work with callback functions. So, in order to use your
callback functions from the past, you can wrap them with run.promisify()
.
For example, the setlist
promisify will wrap the file system fs.readFile()
function so it can be chained in our generator function.
// Import fs library
const fs = require('fs');
// Create generator function
function* readConfig(filename) {
// Get file content
let content = yield run.promisify(fs.readFile)(filename);
// Do something with it
return processFileContent(content);
}
// Or do with promise style
run.promisify(fs.readFile)(filename)
.then(function(content) {
return processFileContent(content);
});
If you are planning to write down classes or objects with generator function,
you can transform them into Promise on runtime by calling run.proxify()
and pass in your class or object after the class definition.
For class object,it will also automatically transform the prototype object.
Note that you should convert extended class with the proxify
if you define
new generator function in the extended class.
class baseClass {
* method() {
...
}
static * staticMethod() {
...
}
}
// Convert base class
run.proxify(baseClass);
class extendedClass extends baseClass {
* extMethod() {
...
}
}
// Convert extended class
run.proxify(extendedClass);
// No need to convert because there is no generator functions
class anotherClass extends baseClass {
syncMethod() {
...
}
}
// But this calls will return promise because we already
// transform the base class
anotherClass.staticMethod();
Some function that require callback handler, such as REPL eval function, are more reliably written with generator function, at least in my opinion.
To wrap the generator function so it can be used with the callback handler
you can use run.callbackify()
function.
// Import REPL library
const repl = require('repl');
// Create repl eval callback handler
function* evalHandle(cmd) {
// Get result from evaluated code
let result;
try {
let result = eval(cmd);
} catch(error) {
// Get recoverable status (See REPL documentation from Node.js)
if (isRecoverable(error)) {
return REPL.Recoverable(error);
} else {
// You can just throw error here and the callbackify will properly
// pass the error to the callback
throw error;
}
}
// Return result to the callback if the eval suceeds
return result;
}
// Start repl session
repl.start({ eval: run.callbackify(evalHandle) });
You can test the package by
npm test
It may not the best testing in the world as this is my first project that using a real proper test.
If you encounter any issues related to the Setlist library, please report them at https://github.com/withmandala/setlist/issues
Copyright (c) 2016 Fadhli Dzil Ikram. SetlistJS is MIT licensed.