🙏 Swear

Flexible promise handling with Javascript:
const name = await swear(fetch('/some.json')).json().user.name;
console.log(name);
const error = await swear(readFile('./error.log')).split('\n').pop();
console.log(error);
Features:
- Extends native Promises; you can treat them as promises with
await
, .then()
and .catch()
. - Automatic Promise.all() for arrays.
- Chainable interface that allows for a scripting syntax like jQuery.
- Extends the API of the Promise value so it's intuitive to follow.
- Can transparently wrap an async function to make it use swear().
See how swear()
compares to native promises when you have some async operations:
const value = await swear(data).map(op1).filter(op2).map(op3);
const value = await Promise.all(data.map(op1)).then(files => files.filter(op2)).then(files => Promise.all(files.map(op3)));
let value = await Promise.all(data.map(op1));
value = value.filter(op2);
value = await Promise.all(value.map(op3));
Note that in the example above, op2
has to be sync since the native .filter()
cannot deal with an async one, but with swear()
you can do .filter(async item => {...})
as well! Keep reading 😄
API
The coolest bit is that you already know the API since it uses the native Javascript one! You can call the methods, properties, etc. of the value that you pass to swear() as you would do normally:
const value = await swear(getPi()).toFixed(1).split('.').map(n => n * 2).join('.');
console.log(value);
const name = await swear(fetch('/some.json')).json().user.name;
console.log(name);
const res = await fetch('/some.json');
const data = await res.json();
const user = data.user;
const name = user.name;
console.log(name);
Number
The Number documentation explains the native API that is available. For instance, let's see with .toFixed()
:
async function getPi() { }
const pi = await swear(getPi()).toFixed(2);
console.log(pi);
You can apply then other string operations as well, for instance you might want to extract some decimals:
const decimals = await swear(getPi()).toFixed(3).split('.').pop();
console.log(decimals);
String
The String documentation explains the native API that is available. For instance, let's see with .split()
:
async function getCsv(url) { }
const first = await swear(getCsv('/some.csv')).split('\n').shift().split(',');
console.log(first);
Function
If you pass a function, swear will return a function that, when called, will return a swear instance. It transparently passes the arguments (resolving them if needed!) and resolves with the returned value:
const getSomeInfo = swear(async () => {
...
});
const names = getSomeInfo('users.json').map(user => user.name).join(',');
This is great if you want to write a library with swear; it will behave the same as the async version when treated like such; but has a lot of new useful functionality from the underlying values.
Array
We are extending native arrays by adding async and RegExp methods to them:
const value = await swear([0, 1, 2]).filter(async n => n * 2 < 2);
console.log(value);
const value = await swear(['a', 'b', 'C']).find(/c/i);
console.log(value);
Note: don't worry, we are not touching the prototype. These extensions are only available until you call await
, .then()
or .catch()
.
For sync methods they behave the same way as the native counterparts. For async
methods you need to be aware whether each of those callbacks is called in parallel (concurrent) or in series:
.every()
: series, "executes the provided callback function once for each element present in the array until it finds one where callback returns a falsy value" - MDN Documentation..filter()
: parallel.find()
: series.findIndex()
: series.forEach()
: parallel.map()
: parallel.reduce()
: series.reduceRight()
: series.some()
: series, "executes the callback function once for each element present in the array until it finds one where callback returns a truthy value" - MDN Documentation..sort()
: none. This method is not modified and it does not accept an async callback.
The ones called in series is because later iterations might depend on previous ones.
Examples
Note: all these must be run in an async
context. I am using the great libraries mz/fs
and got
.
This library is specially useful if we want to do things like fetching urls, mapping their arrays, working with strings, etc. For instance, let's read all the files in the current directory:
const files = await swear(readdir(__dirname)).map(file => readFile(file, 'utf-8'));
const files = await readdir(__dirname).then(files => files.map(file => readFile(file, 'utf-8')));
const files = await dir(__dirname).map(read);
Retrieve a bunch of websites with valid responses:
const urls = ['francisco.io', 'serverjs.io', 'umbrellajs.com'];
const websites = await swear(urls)
.map(url => got(url))
.map(res => res.body)
.filter(Boolean);
const urls = ['francisco.io', 'serverjs.io', 'umbrellajs.com'];
const responses = await Promise.all(urls.map(url => got(url)));
const websites = responses
.map(res => res.body)
.filter(Boolean);
Works with any value that a promise can resolve to:
const sum = await swear(got('example.com/data.csv'))
.split('\n')
.filter(Boolean)
.map(line => line.split('\t').shift())
.map(num => parseFloat(num, 10))
.reduce((total, num) => total + num, 0);
Calculate pi in true parallel with Monte Carlo Method and uwork
:
const getPi = uwork(function findPi(number = 10000) {
let inside = 0;
for (let i = 0; i < number; i++) {
let x = Math.random(), y = Math.random();
if (x * x + y * y <= 1) inside++;
}
return 4 * inside / number;
});
const parallel = [getPi(), getPi(), getPi(), getPi()];
const pi = await swear(parallel).reduce((pi, part, i, all) => pi + part / all.length, 0);
TODO
It would be nice to have this API, but it's not planned for now:
const findAll = swear((...args) => {...}).map(a => a * 2);
const all = await findAll(...args);
Acknowledgements
Libraries based on this:
atocha
: run terminal commands from Node.js.create-static-web
: another static site generator.fch
: an improved version of fetch().files
: Node.js filesystem API easily usable with Promises and arrays.