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

then-ae

v0.0.3

Published

Then-utilities for ES6 promises.

Downloads

5

Readme

Ae

Then æsthetics - helper functions for working with js promises.

Usage

npm install --save then-ae

var ae = require('then-ae');

Motivation

In my experience with working with js promises over the last 2 years, I learned to appreciate a certain programming style.

At first, my code looked pretty much like the regular callback-hell based code.

readSomeSettings
	.then(function (settings) {

		var relevantPieceOfSettings = extractRelevantPiece(settings);

		return fetchDataBasedOnSettings(relevantPieceOfSettings)
			.then(function (dataInJSONFormattedText) {

				var data = JSON.parse(dataInJSONFormattedText);

				return doStuffWithIt(data);
			});
	});

The reason was that I was more comfortable working with plain data instead of promises as soon as I had the chance.

Consider this way of doing it instead:

readSomeSettings
	.then(extractRelevantPiece)
	.then(fetchDataBasedOnSettings)
	.then(JSON.parse)
	.then(doStuffWithIt)

Instead of trying to work in the "normal" synchronous world, all data is returned as soon as possible to be wrapped up in a promise. From there, then is used to transform it further.

This is a contrived example, but I find that this style of coding makes the code more readable, writable and maintainable.

However, common methods like map and reduce don't work well with then. This library fixes that.

Have a look at this less contrived example. Starting with this callback hell-ish implementation, let's refactor to a more æsthetic style.

fetchListOfUrls
	.then(function (urlsJson) {

		return Promise.all(JSON.parse(urlsJson).map(function (url) {

			return fetchUrl(url);
				.then(function (urlContent) {

					return saveToDisk(urlContent);
				})
		}))
	});

It's 3 scopes deep and just barely readable. In a real application it would be even worse. We can start with the low hanging fruit by breaking out JSON.parse and Promise.all.

fetchListOfUrls
	.then(JSON.parse)
	.then(function (urls) {

		return urlsJson.map(function (url) {

			return fetchUrl(url);
				.then(function (urlContent) {

					return saveToDisk(urlContent);
				})
		}))
	})
	.then(Promise.all.bind(Promise));

I think this is better separation of concern. Now we don't need those nested scopes. Let's un-nest them.

fetchListOfUrls
	.then(JSON.parse)
	.then(function (urls) {

		return urls.map(fetchUrl);
	})
	.then(function (urlsContent) {

		return urlsContent.map(saveToDisk);
	})
	.then(Promise.all.bind(Promise));

Shallower and clearer, but with lots of boilerplate noise. All the function () {} and return (which is easy to miss, but hard to debug, by the way) just serve to confuse the meaning of the actual code.

You might have noticed how both anonymous functions are nearly identical, and completely pointless. Let's use an implementation of mapthat works with then. Also, Promise.all.bind(Promise) looks horribly redundant.

fetchListOfUrls
	.then(JSON.parse)
	.then(ae.map(fetchUrl))
	.then(ae.map(saveToDisk))
	.then(ae.all);

Nice.

API

ae.method(methodName)(...arguments)

Arguments

methodName: String

Operates On

Object

Description

A curried function to call the method named methodName with the arguments in the second function application.

Since it is curried, you can easily implement proxy functions for any method.

Without ae:

promiseOfArray
	.then(function (array) {

		return array.slice(5);
	})

With ae:

var slice = ae.method('slice');

promiseOfArray
	.then(slice(5))

Proxies for Array.prototype.map, .filter, .reduce and .join

Arguments

The same arguments you would pass to their Array.prototype counterpart.

Operates On

any[]

Description

Use these to manipulate a promise resolving with an array, much like you would in a synchronous setting.

Without ae:

promiseOfArrayOfStrings
	.then(function (arrayOfStrings) {

		return arrayOfStrings
			.map(function (string) {

				return string.toUpperCase();
			})
			.join(', ');
	})

With ae:

promiseOfArrayOfStrings
	.then(ae.map(function (string) {

		return string.toUpperCase();
	}))
	.then(ae.join(', '))

Or slightly shorter:

promiseOfArrayOfStrings
	.then(ae.map(ae.method('toUpperCase')()))
	.then(ae.join(', '))

ae.object(propertyNames)

Arguments

propertyNames: string[]

Operates On

any[]

Description

Turns an array into an object with property names specified by the argument.

Can optionally be called with multiple parameters instead of an array, like ae.object(propertyName1 [, propertyName2...]).

Without ae:

somePromise
	.then(function (results) {

		return {
			someProperty:     results[0],
			anotherProperty:  results[1]
			lotsOfProperties: results[2]
			iCouldGoOn:       results[3]
		};
	})

With ae:

somePromise
	.then(ae.object(
		'someProperty',
		'anotherProperty',
		'lotsOfProperties',
		'iCouldGoOn'
	))

With Promise.all:

Promise.all([
	doThis,
	doThat,
	doSomethingElse
])
	.then(ae.object([
		'this',
		'that',
		'somethingElse'
	]))

ae.assert(assertionCallback, errorMessage)

Arguments

assertionCallback: function(any) => boolean errorMessage: String

Operates On

any

Description

Use the assertion callback to check the result of the previous promise. If you return false, an error with the message errorMessage will be thrown.

Without ae:

userPromise
	.then(function (user) {

		if (!user.hasPermission) {

			throw new Error('User missing permission.');
		}
	})

With ae:

userPromise
	.then(ae.assert(
		function (user) { return user.hasPermission; },
		'User missing permission.'
	))

With ES6:

userPromise
	.then(ae.assert(
		user => user.hasPermission,
		'User missing permission.'
	))

ae.pipeline

Arguments

No arguments. Don't call it, just pass it in.

Operates On

An array of Promise generators. (function => Promise)[]

Description

Takes an array of promise generators. Runs each promise generator sequentially, and passes the result of each one into the next. The resulting promise contains the result of the last generated promise. If any generated promise rejects, the pipeline ends there (no more generator is executed), and the resulting promise is rejected with the error of the failed promise.

Without ae:

arrayOfPromiseGeneratorsPromise
	.then(function (arrayOfPromiseGenerators) {

		return arrayOfPromiseGenerators
			.reduce(
				function (soFar, next) {

					return soFar.then(next);
				},
				Promise.resolve()
			);
	})

With ae:

arrayOfPromiseGeneratorsPromise
	.then(ae.pipeline)

ae.all

Arguments

No arguments. Don't call it, just pass it in.

Operates On

Promise[]

Description

Simply a bound version of Promise.all, so that it can be passed to then.

Without ae:

arrayOfPromisesPromise
	.then(Promise.all.bind(Promise))

With ae:

arrayOfPromisesPromise
	.then(ae.all)

ae.sequence

Arguments

No arguments. Don't call it, just pass it in.

Operates On

An array of Promise generators. (function => Promise)[]

Description

Takes an array of promise generators. Runs the promise generators in sequence and resolves with an array containing the results of each promise. Much like Promise.all but not in parallel. If any of the generated promises rejects, the resulting promise is rejected with the error of the failed promise. The error will have an array partialSequenceResults containing the results of the promises that resolved successfully.

Useful when you need to make sure thing are executed in order. Like db inserts following deletes, etc.

Without ae:

Way too much error prone code.

With ae:

arrayOfPromiseGenerators
	.then(ae.sequence)

ae.parallel(numWorkers)

Arguments

numWorkers: number

Operates On

An array of Promise generators. (function => Promise)[]

Description

Takes an array of promise generators. Runs the promise generators in parallel, but limited to numWorkers "threads" at any time. If any of the generated promises rejects, all workers are canceled, and the resulting promise is rejected with the error of the failed promise.

Useful when you need to convert a gazillion image files, or any other task you'd like to run in parallel, but that would use too much resources to do all at once.

Without ae:

Seriously? I don't even know. Probably close to what ae implements, copy-pasted from Stack Overflow. :P

With ae:

arrayOfResourceIntensivePromiseGenerators
	.then(ae.parallel(4))