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

await

v0.2.6

Published

Set-theoretical promises

Downloads

8,851

Readme

await.js

await.js is a lightweight, dependency-free promises library that makes both serial and parallel logic easy by thinking in terms of sets. You await() a set of things, and once you have all the things, you do stuff. await.js conforms to the Promises/A+ spec.

Example

// await this set of things
var getThings = await('me','feed','ready')

// fulfill 'me'
$.ajax('/api/users/me', {
  success: function(data){ getThings.keep('me', data) },
  error: function(err) { getThings.fail(err) }
})

// fulfill 'feed'
$.ajax('/api/users/me/feed', {
  success: function(data){ getThings.keep('feed', data) },
  error: function(err) { getThings.fail(err) }
})

// fulfill 'ready'
$(document).ready(function(){
  getThings.keep('ready');
})

// do stuff with the things
getThings.then(function(got){
  // now you got stuff
  got.me // json object
  got.feed // json object
  got.ready // null; dom has loaded
},function(err){
  // oops, there was an error
});

Installation and use

Node.js:

%> npm install await
%> node
node> var await = require('await')

Browsers:

<script src="path/to/await.js"></script>
<script>
// window.await is defined
</script>

Browsers (AMD/RequireJS):

// window.await is NOT defined
define(['await'], function(await){
  ...
})

Old browser note

You'll need some polyfill or Modernizr goodness to use it in browsers that don't support JavaScript 1.8.5. (e.g. IE8 and lower). To that end, example-polyfills.js is included in the git repo. The polyfills file has no test coverage, and is otherwise purely optional.

How does it work?

An await promise represents a set of empty slots that need to be filled. A promise can be in one of three states: unresolved, kept or failed. Sometimes it's useful to think in terms of it being unresolved or resolved, where resolved means either kept or failed.

A promise starts out in an unresolved state. As soon as each individual slot has been filled, the promise enters the kept state. It doesn't matter how long it takes or in what order they're filled, or whether it's done serially or in parallel.

If something goes wrong during fulfillment, the promise enters the failed state. The promise can't enter the failed state if it has already entered the kept state, or vice versa. Once in either a kept or failed state, a promise will never switch to any other state.

Creating promises

You create a promise by calling the await() function and passing a series of strings; one for each slot you expect to be filled.

var prom = await('foo','bar','baz')

Using promises

You use promises in two ways: the event handlers or the then() method.

The event handlers

An await promise has onkeep(), onresolve() and onfail() methods that accept callbacks. These methods can be called any number of times, at any time, in any order.

These methods can be called whether the promise is resolved or unresolved. If called before resolve, callbacks are stored for later execution. If called after resolve, callbacks are executed immediately.

An important aspect of promises is that, whether or not a promise is resolved, your callback is always executed after the method returns. This means that the semantics of your program don't change based on the state of a promise at any given moment. That is, your code is effectively decoupled from the state of a promise, and you can always rely on it being an asynchronous operation.

promise.onkeep(function(got){
  got.slotA
  got.slotB
  //...
})

promise.onfail(function(err){
  // handle error case
})

promise.onresolve(function(){
  // promise is now either in
  // a kept or failed state
})

Since these methods are chainable, the above could also be written as:

promise.onkeep(function(got){
  //...
}).onfail(function(err){
  //...
}).onresolve(function(){
  //...
})

Progress

There is also an onprogress() method which behaves differently from the above event handlers. onprogress() callbacks that are added before the promise is resolved are stored and executed any number of times (including zero) during progress events. Progress callbacks that are added after the promise is resolved are silently ignored. Progress callbacks are only called while the promise is unresolved. Progress callbacks are passed an object containing the current progress of each slot. This object also has a getAverage() method that returns a number for reporting the overall progress of the promise. This allows you to implement either a multi-progress bar, or a single progress bar. All progress values are numbers between 0.0 and 1.0.

promise.onprogress(function(prog){
  progress.slotA // number between 0.0 and 1.0
  progress.slotB // number between 0.0 and 1.0
  // etc
})

The then(onkeep, onfail, onprogress) method

The then() method conforms to the signature and behavioral contract outlined in the Promises/A+ spec. Unlike the event handlers above, which are purely consumer methods, then() is both consumer and provider. That is, it returns a new promise pending on the value that will eventually be returned by its callback.

promise.then(function(got){
  got.slotA // etc, same as above
  // the value returned here fulfills
  // the promise returned by then()
}, function(err){
  // handle error case
  // the value returned here fulfills
  // the promise returned by then()
  // same as above
}, function(amount){
  // there has been progress indicated
  // by "amount". note that "amount" in
  // this case is an average across all
  // slots of this promise
})

Others have written good explanations on how to use "thenables", as they have come to be called:

  • http://blog.parse.com/2013/01/29/whats-so-great-about-javascript-promises/
  • https://gist.github.com/domenic/3889970
  • http://howtonode.org/promises
  • http://promisesaplus.com/

Accumulating values over serial then()s

It's common to chain thenables for serial operations in order to have sane error handling and code flow. However, since thenables only keep a single value, it's difficult to accumulate values obtained this way without nesting the calls. This is unfortunate since "nested callback hell" is something thenables were supposed to help you avoid.

Since await deals in sets rather than single values, it accumulates values for you, so that you don't have to nest the calls if you don't want. For example:

function getUser() {
  return await('user')...
}
function getFeed() {
  return await('feed')...
}
function getFollowers() {
  return await('followers')...
}

getUser()
.then(function(){
  return getFeed()
})
.then(function(){
  return getFollowers()
})
.then(function(got){
  // do stuff with got.user
  // do stuff with got.feed
  // do stuff with got.followers
})

If there are name collisions, await will prefer recent values over older ones. To avoid collisions, you can use await's map() method, which is documented in more detail later one. Example:

function getFeed(name) { return await('feed')... }

getFeed('fez').map({'feed':'fez'})
.then(function(){
  return getFeed('bob').map({'feed':'bob'})
})
.then(function(){
  return getFeed('ned').map({'feed':'ned'})
})
.then(function(got){
  // got.fez
  // got.bob
  // got.ned
})

Keeping promises (or failing)

promise.keep(name, [value])

Each slot of a promise is fulfilled using the keep() method. keep() must be called once for each slot. Only the first call to keep() for a given slot has any effect on the state of the promise. Subsequent calls are ignored. If no value is given, it defaults to null.

var prom = await('number','foo')
prom.keep('number', 7)
prom.keep('foo')
// prom is now in a kept state
prom.onkeep(function(got){
  got.number // 7
  got.foo    // null
})

promise.fail(error)

At any time, you can call fail() on a promise, passing an error object representing the failure. If the promise is already in a kept or failed state, calls to fail() are silently ignored, and have no effect on the state of the promise. If none or only some slots have been filled, fail() will permanently push the promise into the failed state.

var prom = await('foo')
prom.fail(new Error('Fake error!'))
// prom is now in a failed state
prom.onfail(function(err){
  err.message // 'Fake error!'
})

promise.progress(name, amount)

While the promise is unresolved, you can call this method any number of times to notify any listeners of progress. Calling this method after the promise is resolved is a no-op. name is a string naming the slot that has progressed. amount is a number between 0.0 and 1.0. Await does not enforce progressively higher amounts; it assumes you know what you're doing in this regard. However, it will enforce that amount is a number between 0.0 and 1.0. If amount is not a number and not parseable into a number, it will be treated as zero.

progress() also accepts an object instead of a string and a number. This allows multiple progress values to be reported at once.

// fires two events
promise.progress('foo', .6)
promise.progress('bar', .4)

// fires one event
promise.progress({ foo: .6, bar: .4 })

Grouping promises

await() accepts other promises in addition to strings. In such cases, the newly-created promise is the union of all grouped promises and string arguments.

p1 = await('foo', 'bar')
p2 = await('baz')
p3 = await(p1, p2, 'qux')

p3.onkeep(function(got){
  // do something with got.foo
  // do something with got.bar
  // do something with got.baz
  // do something with got.qux
})

promise.map() returns a new promise with differently-named slots, and can be used to step around name collisions.

p1 = await('model')
p2 = await('model')
p3 = await(
  p1.map({'model':'m1'}),
  p2.map({'model':'m2'})
)

p3.onkeep(function(got){
  // do something with got.m1
  // do something with got.m2
})

await.all(list)

If you have an array of promises of arbitrary length, you can use await.all() to merge them into a single promise.

// 'proms' is an array of await promises
// that have already been created

await.all(proms)
.onkeep(function(gots){

  // 'gots' is a list with a length
  gots.length // number
  gots[0]
  gots[1] (etc)

  // alternatively...
  gots.forEach(function(got){
    got.foo
    got.bar
    // ...
  })
})

Note that in the above example, gots is an object, not a true array, despite having length, 0, 1 (etc) properties. For example it doesn't have mutator methods like push() or splice(). However for convenience, it does inherit several array-like accessor methods from its prototype:

  • forEach() - Similar to array.forEach()
  • map() - Similar to array.map()
  • some() - Similar to array.some()
  • every() - Similar to array.every()
  • reduce() - Similar to array.reduce()
  • slice() - Similar to array.slice()
  • join() - Similar to array.join()

take(promise, [mapping])

(AKA chaining promises)

Promises can be explicitly chained instead of grouped. Here we've declared two promises, and we want to take the outcome of one and plug it into the other:

p1 = await('foo', 'bar', 'baz')
p2 = await('foo', 'bar', 'buz', 'qux')

p1      p2 
===========
foo     foo
bar     bar
baz     buz
        qux

What happens is that p1 can take p2.

p1.take(p2)

p1 now takes p2, and if p2 fails, p1 fails. As you can see, p2 is a different set of things than p1. Here is how p2 maps to p1:

p1      p2 
===========
foo <-- foo
bar <-- bar
baz     buz
        qux

In other words, p1 only took the intersection of itself with p2. Thus when p2 keeps, p1 remains unkept. You can therefore optionally provide a mapping object:

p1.take(p2, {'buz':'baz'})

p1      p2 
===========
foo <-- foo
bar <-- bar
baz <-- buz
        qux

If the mapping you provide conflicts with direct matches, the mapping wins:

p1.take(p2, {
  'buz':'baz',
  'qux':'bar'
})
p1      p2 
===========
foo <-- foo
bar <-- qux
baz <-- buz
        bar

You can also take non-await thenables, such as a Q promise or a jqXHR object, provided that you name the value:

await('feed').take($.ajax('/api/feed'), 'feed')

This is easier than doing:

var prom = await('feed')
$.ajax('/api/feed', {
  success: function(data){ prom.keep('feed', data) },
  error: function(err){ prom.fail(err) }
})

Using nodify() in Node.js

Node.js callbacks have an error object in the first position. If the operation was successful, this argument is null, otherwise it's an instance of Error. Every node callback you write therefore needs an if/else statement in order to see if this argument is not empty, which can get tedious. For example, to hook up an await promise to a node callback, you'd need to do this:

var promise = await('logData')

fs.readFile('/tmp/log', function(err, data){
  if (err) {
    promise.fail(err);
  } else {
    promise.keep('logData', data);
  }
});

As a convenience, you can wrap the callback in promise.nodify(), and it will wire up the error handling automatically, shifting err off the signature for you:

var promise = await('logData')

fs.readFile('/tmp/log', promise.nodify(function(data){
  promise.keep('logData', data);
}));

To save even more typing, if you simply want to keep the promise based on the success value, you can pass a string to nodify() instead of a function. This example below behaves equivalent to the above:

var promise = await('logData')

fs.readFile('/tmp/log', promise.nodify('logData'));

Examples

Callback signature: (error, a, b, c)

nodeApi.doSomething(promise.nodify('foo', 'bar'))
// 'foo' and 'bar' are kept with values a and b, respectively.
c is ignored

Callback signature: (error)

nodeApi.doSomething(promise.nodify('foo', 'bar'))
// 'foo' and 'bar' are both kept with value null

Callback signature (error, a, b)

nodeApi.doSomething(promise.nodify(null, 'foo'))
// 'foo' is kept with value b, a is ignored

API overview