Sophisticated and functionally-minded async with advanced features: coroutines, promises, ES2015 iterables, fantasy-land.
Creed simplifies async by letting you write coroutines using ES2015 generators and promises, and encourages functional programming via fantasy-land. It also makes uncaught errors obvious by default, and supports other ES2105 features such as iterables.
Using creed coroutines, ES2015, and FP to solve the async-problem:
'use strict';
import { runNode, all, coroutine } from 'creed';
import { readFile } from 'fs';
import { join } from 'path';
// joinPath :: String -> String -> String
const joinPath = init => tail => join(init, tail);
// readFileP :: String -> String -> Promise Error Buffer
const readFileP = encoding => file => runNode(readFile, file, {encoding});
// pipe :: (a -> b) -> (b -> c) -> (a -> c)
const pipe = (f, g) => x => g(f(x));
// concatFiles :: String -> Promise Error String
const concatFiles = coroutine(function* (dir) {
const readUtf8P = pipe(joinPath(dir), readFileP('utf8'));
const index = yield readUtf8P('index.txt');
const results = yield all(index.match(/^.*(?=\n)/gm).map(readUtf8P));
return results.join('');
});
const main = process => concatFiles(process.argv[2])
.then(s => process.stdout.write(s));
main(process);
npm install --save creed
bower install --save creed
As a module:
// ES2015
import { resolve, reject, all, ... } from 'creed';
// Node/CommonJS
var creed = require('creed');
// AMD
define(['creed'], function(creed) { ... });
As window.creed
:
<!-- Browser global: window.creed -->
<script src="creed/dist/creed.js"></script>
Creed is REPL friendly, with instant and obvious feedback. Try it out in JSBin or using ES2015 with babel, or try it in a REPL:
Note that ES2015 import
currently doesn't work in babel-node
. Use let
+ require
instead.
npm install creed
npm install -g babel-node
babel-node
> let { resolve, delay, all, race } = require('creed');
'use strict'
> resolve('hello');
Promise { fulfilled: hello }
> all([1, 2, 3].map(resolve));
Promise { fulfilled: 1,2,3 }
> let p = delay(1000, 'done!'); p
Promise { pending }
... wait 1 second ...
> p
Promise { fulfilled: done! }
> race([delay(100, 'no'), 'winner']);
Promise { fulfilled: winner }
Create an async coroutine from a promise-yielding generator.
import { coroutine } from 'creed';
function fetchTextFromUrl(url) {
// Fetch the text and return a promise for it
return promise;
}
// Make an async coroutine from a generator
let getUserProfile = coroutine(function* (userId) {
try {
let profileUrl = yield getUserProfileUrlFromDB(userId);
let text = yield fetchTextFromUrl(profileUrl);
return text;
} catch(e) {
return getDefaultText();
}
});
// Call it
getUserProfile(123)
.then(profile => console.log(profile));
type NodeApi e a = ...* → Nodeback e a → ()
type Nodeback e a = e → a → ()
Turn a Node API into a promise API
import { fromNode } from 'creed';
import { readFile } from 'fs';
// Make a promised version of fs.readFile
let readFileP = fromNode(readFile);
readFileP('theFile.txt', 'utf8')
.map(String) // fs.readFile produces a Buffer, transform to a String
.then(contents => console.log(contents));
type NodeApi e a = ...* → Nodeback e a → ()
type Nodeback e a = e → a → ()
Run a Node API and return a promise for its result.
import { runNode } from 'creed';
import { readFile } from 'fs';
runNode(readFile, 'theFile.txt', 'utf8')
.map(String) // fs.readFile produces a Buffer, transform to a String
.then(contents => console.log(contents));
type Producer e a = (...* → Resolve a → Reject e → ())
type Resolve a = a → ()
type Reject e = e → ()
Run a function to produce a promised result.
import { runPromise } from 'creed';
/* Run a function, threading in a url parameter */
let p = runPromise((url, resolve, reject) => {
var xhr = new XMLHttpRequest;
xhr.addEventListener("error", reject);
xhr.addEventListener("load", resolve);
xhr.open("GET", url);
xhr.send(null);
}, 'http://...'); // inject url parameter
p.then(result => console.log(result));
Parameter threading also makes it easy to create reusable tasksthat don't rely on closures and scope chain capturing.
import { runPromise } from 'creed';
function xhrGet(url, resolve, reject) => {
var xhr = new XMLHttpRequest;
xhr.addEventListener("error", reject);
xhr.addEventListener("load", resolve);
xhr.open("GET", url);
xhr.send(null);
}
runPromise(xhrGet, 'http://...')
.then(result => console.log(result));
Merge promises by passing their fulfillment values to a merge function. Returns a promise for the result of the merge function. Effectively liftN for promises.
import { merge, resolve } from 'creed';
merge((x, y) => x + y, resolve(123), resolve(1))
.then(z => console.log(z)); //=> 124
Coerce a value or Thenable to a promise.
import { resolve } from 'creed';
resolve(123)
.then(x => console.log(x)); //=> 123
resolve(resolve(123))
.then(x => console.log(x)); //=> 123
resolve(jQuery.get('http://...')) // coerce any thenable
.then(x => console.log(x)); //=> 123
Lift a value into a promise.
import { fulfill, resolve } from 'creed';
fulfill(123)
.then(x => console.log(x)); //=> 123
// Note the difference from resolve
fulfill(fulfill(123))
.then(x => console.log(x)); //=> '[object Promise { fulfilled: 123 }]'
resolve(fulfill(123))
.then(x => console.log(x)); //=> 123
Make a rejected promise for an error.
import { reject } from 'creed';
reject(new TypeError('oops!'))
.catch(e => console.log(e.message)); //=> oops!
Make a promise that remains pending forever.
import { never } from 'creed';
never()
.then(x => console.log(x)); // nothing logged, ever
Note: never
consumes virtually no resources. It does not hold references to any functions passed to then
, map
, chain
, etc.
Promises/A+ then. Transform a promise's value by applying a function to the promise's fulfillment value. Returns a new promise for the transformed result.
import { resolve } from 'creed';
resolve(1)
.then(x => x + 1) // return a transformed value
.then(y => console.log(y)); //=> 2
resolve(1)
.then(x => resolve(x + 1)) // return transformed promise
.then(y => console.log(y)); //=> 2
Catch and handle a promise error.
import { reject, resolve } from 'creed';
reject(new Error('oops!'))
.catch(e => 123) // recover by returning a new value
.then(x => console.log(x)); //=> 123
reject(new Error('oops!'))
.catch(e => resolve(123)) // recover by returning a promise
.then(x => console.log(x)); //=> 123
Fantasy-land Functor. Transform a promise's value by applying a function. The return value of the function will be used verbatim, even if it is a promise. Returns a new promise for the transformed value.
import { resolve } from 'creed';
resolve(1)
.map(x => x + 1) // return a transformed value
.then(y => console.log(y)); //=> 2
Fantasy-land Apply. Apply a promised function to a promised value. Returns anew promise for the result.
import { resolve } from 'creed';
resolve(x => x + 1)
.ap(resolve(123))
.then(y => console.log(y)); //=> 124
resolve(x => y => x+y)
.ap(resolve(1))
.ap(resolve(123))
.then(y => console.log(y)); //=> 124
Fantasy-land Chain. Sequence async actions. When a promise fulfills, run another async action and return a promise for its result.
let profileText = getUserProfileUrlFromDB(userId)
.chain(fetchTextFromUrl);
profileText.then(text => console.log(text)); //=> <user profile text>
Fantasy-land Semigroup. Returns a promise equivalent to the earlier of two promises
import { delay } from 'creed';
delay(200, 'bar').concat(delay(100, 'foo'))
.then(x => console.log(x)); //=> 'foo'
Create a delayed promise for a value, or further delay the fulfillment of an existing promise. Delay only delays fulfillment: it has no effect on rejected promises.
import { delay, reject } from 'creed';
delay(5000, 'hi')
.then(x => console.log(x)); //=> 'hi' after 5 seconds
delay(5000, delay(1000, 'hi'))
.then(x => console.log(x)); //=> 'hi' after 6 seconds
delay(5000, reject(new Error('oops')))
.catch(e => console.log(e.message)); //=> 'oops' immediately
Create a promise that will reject after a specified time unless it settles earlier.
import { delay } from 'creed';
timeout(2000, delay(1000, 'hi'))
.then(x => console.log(x)); //=> 'hi' after 1 second
timeout(1000, delay(2000, 'hi')); //=> TimeoutError after 1 second
Await all promises from an Iterable. Returns a promise that fulfills with an array containing all input promise fulfillment values, or rejects if at least one input promise rejects.
import { all, resolve } from 'creed';
all([resolve(123), resolve(456)])
.then(x => console.log(x)); //=> [123, 456]
let promises = new Set();
promises.add(resolve(123));
promises.add(resolve(456));
all(promises)
.then(x => console.log(x)); //=> [123, 456]
Returns a promise equivalent to the input promise that settles earliest. If there are input promises that are already settled or settle simultaneously, race prefers the one encountered first in the iteration order.
Note: As per the ES6-spec, racing an empty iterable returns never()
Returns a promise equivalent to the input promise that fulfills earliest. If all input promises reject, the returned promise rejects.
Returns a promise that fulfills with an array of settled promises.
import { settle, resolve, reject, isFulfilled, getValue } from 'creed';
// Find all the fulfilled promises in an iterable
settle([resolve(123), reject(new Error('oops')), resolve(456)])
.map(settled => {
return settled.filter(isFulfilled).map(getValue);
})
.then(fulfilled => console.log(fulfilled.join(','))); //=> 123,456
Returns true if the promise is fulfilled.
import { isFulfilled, resolve, reject, delay, never } from 'creed';
isFulfilled(resolve(123)); //=> true
isFulfilled(reject(new Error())); //=> false
isFulfilled(delay(0, 123)); //=> true
isFulfilled(delay(1, 123)); //=> false
isFulfilled(never()); //=> false
Returns true if the promise is rejected.
import { isRejected, resolve, reject, delay, never } from 'creed';
isRejected(resolve(123)); //=> false
isRejected(reject(new Error())); //=> true
isRejected(delay(0, 123)); //=> false
isRejected(delay(1, 123)); //=> false
isRejected(never()); //=> false
Returns true if the promise is either fulfilled or rejected.
import { isSettled, resolve, reject, delay, never } from 'creed';
isSettled(resolve(123)); //=> true
isSettled(reject(new Error())); //=> true
isSettled(delay(0, 123)); //=> true
isSettled(delay(1, 123)); //=> false
isSettled(never()); //=> false
Returns true if the promise is pending (not yet fulfilled or rejected).
import { isPending, resolve, reject, delay, never } from 'creed';
isPending(resolve(123)); //=> false
isPending(reject(new Error())); //=> false
isPending(delay(0, 123)); //=> false
isPending(delay(1, 123)); //=> true
isPending(never()); //=> true
Returns true if it is known that the promise will remain pending
forever. In practice, this means that the promise is one that was
returned by never()
or a promise that has been resolved to such.
import { isNever, resolve, reject, delay, never, race } from 'creed';
isNever(resolve(123)); //=> false
isNever(reject(new Error())); //=> false
isNever(delay(0, 123)); //=> false
isNever(delay(1, 123)); //=> false
isNever(never()); //=> true
isNever(resolve(never())); //=> true
isNever(delay(1000, never())); //=> true
isNever(race([])); //=> true
Extract the value of a fulfilled promise. Throws if called on a pending or rejected promise, so check first with isFulfilled
.
import { getValue, resolve, reject, never } from 'creed';
getValue(resolve(123)); //=> 123
getValue(reject()); //=> throws TypeError
getValue(never()); //=> throws TypeError
Extract the reason of a rejected promise. Throws if called on a pending or fulfilled promise, so check first with isRejected
.
import { getReason, isFulfilled, resolve, reject, never } from 'creed';
getReason(resolve(123)); //=> throws TypeError
getReason(reject('because')); //=> 'because'
getReason(never()); //=> throws TypeError
Polyfill the global Promise
constructor with an ES6-compliant
creed Promise
. If there was a pre-existing global Promise
,
it is returned.
import { shim } from 'creed';
// Install creed's ES2015-compliant Promise as global
let NativePromise = shim();
// Create a creed promise
Promise.resolve(123);