Coder Social home page Coder Social logo

zousan's Introduction

Promises/A+ logo

Zousan ๐Ÿ˜

A Lightning Fast, Yet Very Small Promise A+ Compliant Implementation


There are already several Promise implementations out there, and modern browsers even have built-in Promises, but none met my goals, which are:

  1. Exceedingly Fast - it had to be the fastest possible implementation, so it could be used excessively throughout a codebase with impunity. (Even games!)
  2. Extremely Small - I will be using this in all my future projects, including mobile, and so fast and efficient code is essential. Also, the less code there is, the less can go wrong.
  3. Clearly written and Documented - I want to clearly see (and clearly demonstrate to others) how the code works, both to build confidence in its correctness, and to make it easier to fix/maintain if/when necessary.
  4. Usable Everywhere - I required compatibility with browsers (both new and old), mobile devices, Node and even make a best effort to work in unknown environments.
  5. Simple Build - No dependencies, few files, dog bone simple. (is that a phrase?)

Check out this blog post called A Promising Start - Embracing Speed and Elegance with Zousan Promises where I describe why and how I created this implementation.

Update: There is now an extension library with some handy functions for dealing with Promises (from Zousan or otherwise): Zousan-plus ๐Ÿ˜โž•

2019 Update: As of version 3.0.0 we no longer define Zousan at the global level by default. (The exception being if you import the UMD version via script element without AMD present.)

Usage

Zousan is distributed both as an ES Module and as a UMD (Universal Module Definition), compliant with AMD and CommonJS module styles, as well as simple browser script includes which define Zousan globally.

Zousan is Promise A+ 1.1 compliant, so any documentation for spec-compliant promises applies to Zousan. There are a couple small additions though - see below. Briefly, the spec-compliant API is:

Constructor

Create a new Promise (often, this promise is returned from a function that provides some asynchronous resource)

	var promise = new Zousan(function(resolve, reject) {
		// ... perform some asynchronous operation ...
		//  load the value to return into "value"
		if(success)
			resolve(value)
		else
			reject(Error("error message goes here"))
	});

then()

To use this promise to obtain the value:

	promise.then(function(value) { // this function is called when promise is resolved
			// do something with your value, you deserve it!

		}, function(err) { // this function is called when promise is rejected
		// bummer...

	})

Extensions

Zousan does have a couple additional features which are not required by the spec, but are very useful when working with promises. Be aware that if you use this "extension" API, your code may not be compatible with other Promise implementations:

catch(onRejected)

catch(onRejected) is equivalent to then(undefined, onRejected) and is just easier to identify - allowing you to adopt the pattern of always ending then chains with a catch, like so:

	getJSON("data.json") 		// hypothetical function which returns a promise
		.then(lookupItems) 		//   takes the data and obtains extra data about items
		.then(updateCount)		//   update item count using host service
		.then(displayResults)	//   update user view of results
		.catch(reportErr)		// Catch any errors occurring in any steps above.

This pattern helps you to remember to always catch any errors produced within your promise chains. Although this isn't part of the Promise A+, it is a very common addition and is present in the ECMAScript 2015 Language Specification.


finally(fn)

finally(fn) is equivalent to then(fn, fn) and is a convenience method for handling both resolved and rejected promises with a single handler. More important than being "convenient" is it is clear in intent - that this function will be run regardless. It is useful in promise chains that require some kind of clean-up operation such as releasing resources:

	getJSON("data.json") 		// hypothetical function which returns a promise
		.then(lookupItems) 		//   takes the data and obtains extra data about items
		.then(updateCount)		//   update item count using host service
		.then(displayResults)	//   update user view of results
		.catch(reportErr)		// Catch any errors occurring in any steps above
		.finally(cleanup)		// Release resources, stop spinner, etc.

This method is not part of the Promise A+ spec nor included in ECMAScript2015 spec


all(promiseArray)

The other addition is a utility function called all() which takes an array of promises and returns a single promise that will resolve when all promises in the array resolve. The value passed to you is an array with the values from each promise respectively. If any promise within the passed array rejects, this will reject with the same error as the original rejection.

Note: The array can contain non-promises as well. They will be passed through to the resolved array as-is.

It is available by calling Zousan.all() (i.e. does not require a promise instance).

For example, to obtain data from a list of sources:

	// define an array with our data locations
	var sources = ["data1.json", "data2.json", "data3.json"]

	// Next, obtain an array of promises using hypothetical getJSON function
	var dataProm = sources.map(getJSON)

	// When all promises resolve, we call processData with array of results
	Zousan.all(dataProm).then(processData, reportError)

This function is also present in the ECMAScript 2015 spec.


timeout(ms[,msg])

This method returns a new promise based on the original that times out (rejects with Error("Timeout") or if specified Error(msg)) if the original promise does not resolve or reject before the time specified (in milliseconds).

	// Create a new promise that times out after 3 seconds
	var prom = new Zousan().timeout(3000)
	prom.then(doit,problem)

Note: This has no effect on the original promise - which may still resolve/reject at a later time. This pattern allows you to create a timeout promise against any existing promise, such as one returned from an API, without disturbing the original promise:

	// Use the getData() function but only wait for a maximum of 2 seconds
	getData(url).timeout(2000).then(process, error)

Note 2: Be careful of promise chains containing multiple timeouts. To trigger handlers at multiple timeout points, use separate statements, like this:

	var data = getData(url)
		.timeout(10000)	// wait a maximum of 10 seconds for the data to return
		.then(process, error)

	data.timeout(1000)	// after one second, display a progress bar
		.catch(displayProgressBar)

	data.timeout(3000)	// after 3 seconds, display a cancel button
		.catch(displayCancelButton)

Convenience resolve() / reject() as Instance Methods

The spec-compliant manner of resolving/rejecting a promise is to call the methods handed back to the constructor function argument, as shown in the constructor example above. Often it is more convenient (or cleaner) to resolve/reject a promise outside the constructor. For this purpose, Zousan provides resolve and reject methods right on the promise instance. So this pattern is available:

	var promise = new Zousan()
	if(success)
		promise.resolve(value)
	else
		promise.reject(Error("error message goes here"))

Convenience utility method to create Resolved or Rejected Promises

These functions create new promises and resolve or reject them with values or errors all in one convenient step.

Note: This differs from the above resolve/reject instance methods in that these are functions which create new promises in a resolved or rejected state, whereas the instance methods of the same names above resolve or reject a previously existing promise (hence, those are instance methods while these are not)

To create a promise and resolve or reject it immediately:

	// Create a promise and resolve it immediately with the value 100
	var resolvedPromise = Zousan.resolve(100)

	// --- Note: The above is equivalent to the following: ---
	var resolvedPromise2 = new Zousan()
	resolvedPromise2.resolve(100)

	// --- or, the following ---
	var resolvedPromise3 = new Zousan(function(res,rej) {
			res(100)
		});

	// Create a promise and reject it immediately with the stated error
	var rejectedPromise = Zousan.reject(Error("Security Error"))

Uncaught Rejections Warning

By default, Zousan will log a warning message to the console if a promise is rejected and that rejection is not "caught". Generally, it is best to use the catch() pattern shown above, which will ensure all rejections are handled. If you forget, you will upset Zousan, and he will remind you.

Zousan assigns Zousan.warn to console.warn and uses that to log the uncaught rejection message. You may use an alternate handler for these warnings by assigning Zousan.warn to the function of your choice.

If you wish to suppress this warning, you can turn it off globally via:

	Zousan.warn = function() { }

You may also suppress the warning for a specific promise by setting the promise's handled property to true:

	const p = new Zousan()
	p.handled = true // don't warn when we reject this
	p.reject(123)

FAQ

Q: What does "Zousan" mean?

Well, if you had a 3-year-old Japanese child, you would know, now wouldn't you!? "Zou" is the Japanese word for "Elephant". "San" is an honorific suffix placed after someone's name or title to show respect. Children (and other kawaii people) often put "san" after animal names as a sign of respect to the animals.. and just to be kawaii.

Here is a video that might help

And if you need more guidance (or just enjoy these as much as I do) here is another - Zousan Da-ta!!

Q: Ok, cute - but why name it after an Elephant?

Because elephants never forget. So you can depend on them to keep their promises!

Q: Why did you write another Promise implementation?

I briefly explained why at the top of this README - but for a more detailed explanation, check out my blog post on the subject.

Q: How did you make it run so fast?

I discuss that a bit on my Zousan blog post as well.

Q: Just how fast is it?

I set up a jsperf comparison between:

  • Zousan (2,160 bytes minified)
  • Bluebird (72,282 bytes minified) - Considered the king of high-performance Promises
  • When (12,474 bytes minified) - Long established and high performance Promise shim
  • PinkySwear (842 bytes minified) - The smallest compliant Promise implementation I've come across
  • covenant (3,335 bytes) - A Promise implementation written in CoffeeScript
  • Native Promises - Built into all recent browsers except IE.

Note: Graph illustrates operations per second, so longer bars are better.

License

See the LICENSE file for license rights and limitations (MIT).

zousan's People

Contributors

alubbe avatar bitespresso avatar bluejava avatar bryant1410 avatar maxnordlund avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zousan's Issues

Feature requests: Promisification and .finally

Hey Glenn,
thanks for exploring the options for a small, high performance Promise library!

Just to let you know, I think it would be very helpful to have some kind of a promisification handler because both in the front-end and back-end you will frequently have to promisify the standard library.

My second most favourite thing would be a .finally handler.

Are those something you could envision for this repo?

Better benchmarks

I found this jsperf benchmark, which I think is way too simple of a use case: http://jsperf.com/bluebird-vs-rsvp/89

So I added your library to the bluebird benchmark suite, which tests a set of small 1ms I/O operations instead of the synchronous .resolve(1).
To run them, just clone the repo, run npm install the main repo and the benchmark directory and run ./bench doxbee and ./bench parallel.

These are the result I am getting back - let me know what you think of these numbers:

Doxbee:
results for 10000 parallel executions, 1 ms per I/O op

file time(ms) memory(MB)
callbacks-baseline.js 227 43.60
promises-bluebird-generator.js 266 33.36
promises-bluebird.js 425 47.75
promises-cujojs-when.js 446 71.76
promises-tildeio-rsvp.js 520 66.28
promises-bluejava-zousan.js 551 71.25
callbacks-caolan-async-waterfall.js 692 76.12
promises-lvivski-davy.js 706 108.98
promises-dfilatov-vow.js 838 155.99
promises-calvinmetcalf-lie.js 858 165.59
generators-tj-co.js 1153 147.88
promises-ecmascript6-native.js 1288 176.08
promises-obvious-kew.js 1397 235.82
promises-then-promise.js 1650 217.07
promises-medikoo-deferred.js 1973 172.57
observables-Reactive-Extensions-RxJS.js 2895 287.86
promises-kode4food-welsh.js 3535 80.52
observables-pozadi-kefir.js 5909 189.24
observables-caolan-highland.js 6860 548.75
promises-kriskowal-q.js 8323 717.75
observables-baconjs-bacon.js.js 20751 846.07

Parallel:
results for 10000 parallel executions, 1 ms per I/O op

file time(ms) memory(MB)
promises-bluebird.js 491 106.86
promises-bluebird-generator.js 626 110.79
callbacks-baseline.js 670 42.71
promises-cujojs-when.js 950 164.30
promises-tildeio-rsvp.js 964 205.40
promises-bluejava-zousan.js 1050 212.48
callbacks-caolan-async-parallel.js 1248 214.15
promises-lvivski-davy.js 1702 267.55
promises-calvinmetcalf-lie.js 1844 354.80
promises-dfilatov-vow.js 2790 512.38
promises-ecmascript6-native.js 3227 519.22
promises-then-promise.js 3299 670.76
promises-kode4food-welsh.js 3725 210.52
promises-medikoo-deferred.js 3931 507.14
promises-obvious-kew.js 9455 960.55

Catch errors in the constructor

It doesn't seem to be part of Promises/A+, but browsers, bluebird, q and so on's promises do this. I would like Zousan to catch errors and reject the promise, too.

Bug when

var a = new Zousan();
var b = new Zousan();
a.then(b.resolve, b.reject);
b.then(()=>console.log('hi'), ()=>console.log('bye'))

This doesn't work, it has something to do with call(_undefined, ...)

Publish v2.5.0 to NPM

First of all, thanks for your incredible library! I've tested it against more updated versions of Later, When, PinkySwear, BlueBird, etc, and Zousan is still the fastest by a large margin!

But the published version on NPM is the 2.3.3, not 2.5.0.

Thanks again!

Considerable slowness on node environment in comparison to bluebird

Using this test code:

var Zousan = require('zousan');
var Bluebird = require('bluebird');

var bluebirds = [];
var zousans = [];

console.time('zousan');

for(var i = 0; i < 500000; i++) {
  zousans.push(new Zousan(function(resolve, reject) {
    resolve(1);
  }));
}

Zousan.all(zousans).then(() => {console.timeEnd('zousan')});

// ----------------------

console.time('bluebird');

for(var j = 0; j < 500000; j++) {
  bluebirds.push(new Bluebird(function(resolve, reject) {
    resolve(1);
  }));
}

Bluebird.all(bluebirds).then(() => {console.timeEnd('bluebird')});

And this environment:

[3/5.2]{1}niwi@kaleidos:~/tmp/speedtest> npm ls
/home/niwi/tmp/speedtest
โ”œโ”€โ”€ [email protected]
โ””โ”€โ”€ [email protected]
[3/5.2]niwi@kaleidos:~/tmp/speedtest> node --version
v5.10.1

The results are:

[3/5.2]niwi@kaleidos:~/tmp/speedtest> node speedtest.js
zousan: 1112.415ms
bluebird: 440.596ms

Source in ES Modules?

Thanks for your amazing lib in such a small size.
Any plan to provide an ES module form source?

The code is wrapped by UMD now. It's usable but forces to set the global name. And the AMD checking may also leads to some issues in special environment.

Since it's such a small library, including it in the upper application is useful. But the form now prevents tree-shaking etc.

I copy the source code into my project so far. Hope to use it as a dependency.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.