npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

wate

v0.4.0

Published

Wrangle callbacks without Promises.

Downloads

12

Readme

                           /$$
                          | $$
 /$$  /$$  /$$  /$$$$$$  /$$$$$$    /$$$$$$
| $$ | $$ | $$ |____  $$|_  $$_/   /$$__  $$
| $$ | $$ | $$  /$$$$$$$  | $$    | $$$$$$$$
| $$ | $$ | $$ /$$__  $$  | $$ /$$| $$_____/
|  $$$$$/$$$$/|  $$$$$$$  |  $$$$/|  $$$$$$$
 \_____/\___/  \_______/   \___/   \_______/

Wate is a small (2.2kb minified and gzipped), fast, full-featured, easy-to-debug control flow library for JavaScript and TypeScript. Rather than using Promise-based libraries that swallow errors and lose stack traces in production, use Wate to manage callbacks inside of lightweight Futures.

Wate also exposes functions that convert to and from Promises and Futures, so you can interop with Promise libraries if needed.

Install It

npm install --save wate

If you're using TypeScript, the type definitions should Just Work and be imported into your project automatically thanks to the "typings" definition in the package.json.

Examples

Wrap a Node function to make it return Futures:

const fs = require('fs');

function readFile(filename, encoding) {
  return wate.make((callback) => {
    fs.readFile(filename, encoding, callback);
  });
}

Transform a value as soon as it's loaded:

const xmlParser = require('xml2json');

// Read the file using the fn we defined above
const file = readFile('config.xml', 'utf-8');

// Transform to JSON
const json = wate.transform(file, (xmlText) => xmlParser.toJson(xmlText));

// Print the JSON
json.done((err, jsonVal) => {
  console.log(jsonVal);
});

Read some files concurrently and print them when you're done:

// Read in parallel
const proust = readFile('proust.txt', 'utf-8');
const hemingway = readFile('hemingway.txt', 'utf-8');

// Get all the of values out of the futures once they've loaded
wate.splat([ proust, hemingway ], (proustText, hemingwayText) => {
  // Print the values
  console.log('proust said', proustText);
  console.log('hemingway said', hemingwayText);
});

Read and parse some files concurrently, and operate on them both when you're done:

function loadJson(filename) {
  const file = readFile(filename, 'utf-8');
  return wate.transform(file, JSON.parse);
}

// Read and parse the two files in parallel
const conf = loadJson('config.json');
const overrides = loadJson('overrides.json');

// Combine the parsed hashes once they've loaded
const fullConf = wate.transform([conf, overrides], (confHash, overrideHash) => {
  return _.extend({}, confHash, overrideHash);
});

// Print the combined hash when it's ready
fullConf.done((err, val) => {
  console.log(val);
});

API

Futures

Futures expose two methods:

.done(callback)

Given a callback of the form function(err, val) {}, calls the callback with its error and value states when it resolves. For example:

// Returns a future for the file
const fileFuture = readFile('test.txt', 'utf-8');

fileFuture.done((err, text) => {
  // Ordinary Node-style callback.
  if(!err) console.log(text);
});

.catch(callback)

Given a callback of the form function(err) {}, calls the callback with its error if the future resolves to an error. For example:

const fileFuture = readFile('test.txt', 'utf-8');

fileFuture.catch((err) => {
  // Only runs if there's an error
});

There isn't a corresponding method that only runs when the future succeeds, because if you're going to ignore errors you should be explicit about it.

Composing Futures

wate.all(futures)

Returns a Future that waits until all the given futures are successful, or errors out as soon as the first given Future does. For example:

const proust = readFile('proust.txt', 'utf-8');
const hemingway = readFile('hemingway.txt', 'utf-8');

wate.all([ proust, hemingway ]).done((err, texts) => {
  if(!err) {
    // Note: the below is a silly way to do this, and Wate provides better
    // syntax for this so that you wouldn't write your code this way. But for
    // the sake of a gradual introduction:
    console.log(texts[0]); // proust
    console.log(texts[1]); // hemingway
  }
});

wate.none(futures)

Returns a Future that collects all of the errors and returns them in an array as the err parameters to its done function, or succeeds if any of the futures succeed.

const planA = runPlan('a');
const planB = runPlan('b');

wate.none([ planA, planB ]).done((allFailures, succeeded) => {
  if(!succeeded) {
    console.log("we're doomed");
    console.log(allFailures);
  }
});

wate.settled(futures)

Returns a Future that waits until all of the given futures have either errored out or succeeded. The future will resolve to a list of Result objects of the form { value: val, error: err }. For example:

wate.settled([ a, b, c ]).done((_, results) => {
  results.forEach((result) => {
    if(result.error) console.log('error', result.error);
    else console.log('success', result.value);
  });
});

wate.firstValue(futures)

alias: wate.first

Returns a Future that resolves to the first value that any of the given futures resolve to, or to an array of all of the errors if none of the futures are successful. For example:

wate.firstValue([ a, b, c ]).done((errors, first) => {
  if(!errors) console.log('value of first to finish successfully:', first);
  else console.log('all futures errored:', errors);
});

wate.firstError(futures)

Returns a Future that resolves to the first error that any of the given Futures error out with, or to an array of all of the values if none of the Futures error out. For example:

wate.firstError([ a, b, c ]).done((values, firstError) => {
  if(firstError) console.log('first error', firstError);
  else console.log('no errors, values were:', values);
});

wate.lastValue(futures)

alias: wate.last

Returns a Future that resolves to the last value that the given Futures resolve to, or an array or all of the errors if none of the futures are successful. For example:

wate.lastValue([ a, b, c ]).done((errors, lastValue) => {
  if(!errors) console.log('last value to succeed was:', lastValue);
  else console.log('all futures errored:', errors);
});

wate.concatValues

alias: wate.concat

Given an array of Futures that resolve to arrays of values, returns a Future that resolves to the concatenation of all of the values. For example:

const a = wate.value([ 10, 20 ]);
const b = wate.value([ 30, 40 ]);
const c = wate.value([ 50, 60 ]);

wate.concat([ a, b, c ]).done((err, values) => {
  // values is [ 10, 20, 30, 40, 50, 60 ]
});

wate.concatErrors

Similar to wate.concatValues, but for errors.

Working with Futures

wate.splatValues(future, callback)

alias: wate.spreadValues

Given a Future that will resolve (if successful) to an array of values, calls the callback with the values as an argument list. Returns the given future. For example:

const red = readFile('red.txt', 'utf-8');
const blue = readFile('blue.txt', 'utf-8');

wate.splatValues(wate.all([ red, blue ]), (redText, blueText) => {
  console.log(redText);
  console.log(blueText);
});

wate.splatErrors(future, callback)

alias: wate.spreadErrors

Given a Future that will resolve (on errors) to an array of errors, calls the callback with the errors as an argument list. Returns the given future. For example:

const explode = readFile('none.txt', 'utf-8');
const alsoExplode = readFile('none.txt', 'utf-8');

wate.splatErrors(wate.firstValue([ explode, alsoExplode ]), (err1, err2) => {
  console.log(err1);
  console.log(err2);
});

wate.splat(futures, callback)

aliases: wate.splatAll, wate.spreadAll, wate.spread

Given an array of futures and a callback, calls the callback with an argument list of values if the futures all succeed. The values will be passed into the callback in the order that the futures were passed in the array. Returns a future that succeeds if all of the futures succeed, or fails if any of them fail. For example:

wate.splat([ a, b, c ], (aValue, bValue, cValue) => {
  console.log('a:', aValue);
  console.log('b:', bValue);
  console.log('c:', cValue);
});

This is just a convenient wrapper around a common pattern:

wate.splatValues(wate.all([ a, b, c ]), (aValue, bValue, cValue) => {
  console.log('a:', aValue);
  console.log('b:', bValue);
  console.log('c:', cValue);
});

Since the all future is returned, this allows for easy error handling:

wate.splat([ a, b, c ], (aValue, bValue, cValue) => {
  // handle values here
}).catch((err) => {
  // handle errors here
});

wate.transformValue(future, mapper)

aliases: wate.bindValue

Given a future, returns a future that will resolve to the value of the given future, transformed by the given mapper function. For example:

const fileContents = readFile('config.json', 'utf-8');
const config = wate.transformValue(fileContents, (text) => {
  return JSON.parse(text);
});

// Or, more succinctly:
const config = wate.transformValue(fileContents, JSON.parse);

wate.transformError(future, callback)

alias: wate.bindError

Similar to wate.transformValue, but transforms errors.

wate.transformValues(futures, callback)

Given an array of futures and a callback of the form function(...args) {}, passes each of the resolved futures' values into the callback in the order the futures appear in the array and returns a future that will resolve to the value of the callback. For example:

const conf = wate.transform(readFile('config.json', 'utf-8'), JSON.parse);
const overrides = wate.transform(readFile('overrides.json', 'utf-8'), JSON.parse);

const fullConf = wate.transformValues([conf, overrides], (confHash, overrideHash) => {
  return _.extend({}, confHash, overrideHash);
});

wate.transformErrors(futures, callback)

Similar to wate.transformValues, but operates on errors instead of values.

wate.transform(future|futures, callback)

Convenience function: if given a single future and a callback, proxies to wate.transformValue. If given an array of futures and a callback, proxies to wate.transformValues.

wate.unwrapValue(future)

alias: wate.unwrap

Given a future that resolves to another future, unwraps the inner future.

const urlToLoad = readFile("url-to-load.txt", "utf-8");

const networkFuture = wate.transform(urlToLoad, (url) => {
  // For transform() calls, ordinarily we return a value. Here, however, we're
  // returning a future, since we need to make an async request to get
  // the data. That means "networkFuture" future will actually resolve to...
  // another future.
  return wate.make((callback) => {
    networkRequest(url, callback);
  });
});

// Hence, we unwrap the outer future to get at the inner future:
const network = wate.unwrap(networkFuture);

// In practice, you'd probably just use the flatTransform convenience
// function rather than using both transform and unwrap, like so:
const network = wate.flatTransform(urlToLoad, (url) => {
  return wate.make((callback) => {
    networkRequest(url, callback);
  });
});

wate.unwrapError(future)

Similar to unwrapValue, but unwraps a future returned as an error rather than a future that's returned as a value.

wate.flatten(future)

Maximally un-nests the future's resolved value. If you have multiple nested futures, this will return a future that resolves to the innermost future's value (or error out if the futures error out), whereas unwrap only un-nests a single layer. For example:

const ultraNested = wate.value(wate.value(wate.value(10)));

// The following resolves to 10
const flattened = wate.flatten(ultraNested);

// The following resolves to a future that resolves to 10
const unwrapped = wate.unwrap(ultraNested);

wate.flatTransform(future|futures, transformFunction)

alias: wate.flatBind

Composes the transform and flatten calls. Runs flatten both on the input and the output, so you can pass it nested futures and also return nested futures, and have them transparently un-nest. For example:

const urlToLoad = readFile('url-to-load.txt', 'utf-8');

const network = wate.flatTransform(urlToLoad, (url) => {
  return wate.make((callback) => {
    networkRequest(url, callback);
  });
});

network.done((err, val) => {
  // assuming success, val is the value of the network call
});

Since it composes transform, it similarly also works with arrays of futures:

const conf = wate.transform(readFile('config.json', 'utf-8'), JSON.parse);
const overrides = wate.transform(readFile('overrides.json', 'utf-8'), JSON.parse);

const pidfile = wate.flatTransform([conf, overrides], (confHash, overrideHash) => {
  const conf = _.extend({}, confHash, overrideHash);
  return readFile(conf["pidfile"], "utf-8");
});

pidfile.done((err, val) => {
  // assuming success, val is the contents of the pidfile
});

wate.invert(future)

Given a future that resolves to an error, returns a future that resolves to that error as its value. Similarly, given a future that resolves to a value, returns a future that resolves to the value as its error. For example:

const val = wate.value(10);
const inverted = wate.invert(val);
inverted.done((err) => {
  // err is 10 here
});

Creating Futures

wate.make(builder)

Given a callback of the form function(callback) {}, returns a Future that resolves to whatever the callback is called with. For example:

const fs = require('fs');

const fileFuture = wate.make((callback) => {
  fs.readFile('test.txt', 'utf-8', callback);
});

fileFuture.done((err, text) => {
  if(!err) console.log(text);
});

wate.value(val)

Creates a future of a raw value. For example:

const future = wate.value(10);

wate.error(err)

Creates a failed future given a raw error (or any JS value). For example:

const future = wate.error("an error string");

wate.fromDOMElement(domElement)

Given a DOM element that emits 'load' and 'error' events, returns a future that resolves to a null error and the element if it loads, or an error if the element fails to load. For example:

const image = new Image();
image.src = 'test.png';
const imageFuture = wate.fromDOMElement(image);

// Append the image if it loads
imageFuture.done((err, image) => {
  if(!err) document.body.appendChild(image);
});

Promise Interop

wate.fromPromise(promise)

Turns a Promise into a Wate Future. For example:

const promise = somePromiseReturningFn();
const future = wate.fromPromise(promise);

wate.toPromise(future)

Turns a Future into a Promises/A compatible promise. Note that the Promises/A+ spec mandates interop with Promises/A, so this should work even with Promises/A+ libraries. For example:

const future = wate.value(10);
const promise = wate.toPromise(future);