Simple Guide to Javascript Promises

Lessons and Comments

September 2015

contents

Introduction

Requirements

Lessons and samples

introduction

What are Promises? (from the spec)

A promise represents the eventual result of an asynchronous operation. 

The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.

introduction

What are Promises? (from the spec)

A promise represents the eventual result of an asynchronous operation. 

The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.

UGH

introduction

What are Promises? (from the street)

A promise is an Object that represents the eventual result of an asynchronous operation, or any other operation where the return is not instant. 

Because this is JS, we're still dealing with callbacks, but using the then() method makes it easier to see the 'success' and 'reject' flows, and simpler to reason about.

Because they are objects, we can do smart work with them (saving, composing, etc.) when situations require it.

introduction

Why Promises?

Well-argued elsewhere

Simpler to reason about

Some people prefer 'flatter' code

Included natively in ES6 (aka, ES2015)

BTW, Node v4.0, announced 08-Sept, supports them natively

introduction

How can I use Promises?

A few simple lessons (these slides)

A few simple node apps (Github project)

There are jillions of blog posts, tutorials, videos, etc. Some are good; some are confused.

To save you time...

requirements

NodeJS

an Editor

Terminal/shell

Project

github/netaisllc/simple-promises

git clone

npm install

> bluebird, log4js, yargs

NODE MODULES

Bluebird - the premier JS promises library IMHO

Log4JS - a decent JS logging system

Yargs - feature rich CLI parameter library

All seem to be  compatible with Node 0.10.25

LESSON 1

Compare  the simplest possible  to a typical Node callback.

Understand the syntax for registering callbacks on a returned promise.

LESSON 1

someFuncThatRtnsAPromise( )
         .then(function( resolve_results ) { ...  },
               function( reject_results ) { ... }
);

Promise consumer syntax

That is, you call somebody that returns a promise

LESSON 1

The "success" callback is most often used, but is technically optional

The "error" callback is optional. It's also very handy.

someFuncThatRtnsAPromise( )
         .then(function( resolve_results ) { ...  },
               function( reject_results ) { ... }
);

LESSON 1

someFuncThatRtnsAPromise( )
         .then(function( resolve_results ) { ...  },
               function( reject_results ) { ... }
);

But which of .then()'s functions are executed and when?

It depends on whether the called function (someFuncThatRtnsAPromise, in this case) resolves or rejects the promise. A resolved promise triggers the first callback, a rejected promise, the second.

LESSON 1

someFuncThatRtnsAPromise( )
         .then(function( resolve_results ) { ...  },
               function( reject_results ) { ... }
);

One and only one of .then()'s functions are executed.

A promise may be "settled" - meaning it is either resolved or rejected - only once in its lifetime.

LESSON 1 - run it

node index.js

Or if you are on a modern version of npm:

npm run step1

LESSON 2 

Understand rejection handlers and 'rejection bubbling'

Implementing catch-all error handlers

LESSON 2 - Part a

When a promise is rejected, callers of the promise execute the 'reject' callback registered to .then() -- if such is registered.

Inspect and then run Sample 2:

node index-2.js

or    npm run sample2

LESSON 2 - part b

Remember callbacks to .then() are optional. 

So it's possible (even easy) to have cases where a promise is rejected, but there's no reject handler to deal with it.

This is confusing and easy to overlook. It has led to a perception that promises "swallow" errors.

Is this true?

LESSON 2 - part b

Sort of - but not really. 

Either always include both a success handler and an error handler...

Or implement a catch-all reject handler.

This pattern leverages the behavior of 'reject bubbling' (my term), which generally means a rejection continues moving through a chain of promises until it finds an error handler.

LESSON 2 - part b

You see this commonly used when a chain of promises has been constructed, with an error handler "tacked on" to the end to "catch" any rejections that occur anywhere in the chain.

And I've found it helpful to think about a rejection in the middle of a chain as 'rejecting' all promises that follow it in the chain until it finds a rejection handler.

LESSON 2 - part b

Inspect and then run Sample 2b:

Note that two error messages are logged out to the console. Be sure to grok why that is so.

node index-2b.js   or   npm run sample2b

LESSON 3 

Understand promise chaining

Consider style differences when dealing wth chained promises

LESSON 3 

Chaining promises mean connecting them through their returned values, like a 'fluent' api.

This is a cool feature and is very popular in the blogs.

But most articles/tutorials fail to spell out a couple of basic points. (Or maybe I am just dense :0 )

LESSON 3 

Otherwise you couldn't chain them. 

Every .then() returns a promise

The promise .then() returns is actually a new, unique promise in its own right

This is easy to miss because, out of a desire to depict the way-cool fluent "style" bloggers obscure this point.

LESSON 3 

In other words, many examples (like the following) de-emphasize the fact that there are actually (n) promises involved. (Highlight added)

So...what is the value of n?

LESSON 3 

The original promise, returned by getSomeData and promises returned from each of the .then()s.

n = 4

LESSON 3 

This is a subtle point, and it seems many bloggers aren't clear on it.

 

For example, the blog post at right (highlight added):

LESSON 3 

...when you return a simple value inside then(), the next then() is called with that return value. But if you return a Promise inside then(), the next then() waits on it and gets called when that Promise is settled.

OK, true enough in actual practice, but the text makes it seem that it's what You return that defines .then()'s nature.

It's better to say the following:

... and you are allowed to return any value you like.

LESSON 3 

It's not necessary to store every promise in its own variable, especially when you are experienced.

But I recommend it when starting out.

Plus, storing promises in variables allows for interesting compositional uses later on (which is probably beyond out scope.)

LESSON 3 

Inspect and run Sample3:

node index-3.js   or   npm run sample3

The sample uses named variables to store promises and implements the most common promise pattern: serial flow of events.

LESSON 4 

Understand .finally()

LESSON 4 

The best Promise libraries expose a .finally() method that executes regardless of a promise's ultimate settled state (resolved or rejected).

This is a popular way to do final processing, wrap-up or clean-up after a promise chain r other pattern.

LESSON 4 

Inspect and run Sample4 and Sample4b:

node index-4.js   or   npm run sample4

node index-4b.js   or   npm run sample4b

The sample shows .finally() triggering in both a resolved and a rejected state.

LESSON 5 (Advanced)

Understand methods to collect several promises into one

Consider the use case for .join()

LESSON 5 (Advanced)

The Bluebird promise library offers extensive support for promise use-cases.

One fairly common use-case involves processing a collection of promise-based actions, and subsequently acting on the result of all of those actions.

For example, a UI might require (n) api calls to fetch data for different components, and then need to stitch it all together for rendering.

LESSON 5 (Advanced)

To choose the best pattern, consider:

Is the collection of indeterminate-returning actions static or dynamically composed?

Can we take action on any settled or rejected promise, or must we wait until all the promises have settled?

In other words, do we know the api calls we need to make beforehand?

In other words, does everything have to resolve ok before we can proceed? If even one promise rejects, does that change our intention?

LESSON 5 (Advanced)

In Sample5, we have a static list of api calls to make, after which we log data to the console.

The promise pattern best for a static list is .join()

Inspect and run Sample5:

node index-5.js   or   npm run sample5

LESSON 6 (Advanced)

Consider the use case for .all()

Understand Bluebird's promisifyAll feature

LESSON 6 (Advanced)

Like .join(), the method .all() collects many promises into a single promise representing all settlements.

The difference is .all() is better suited to a dynamic number of input promises, and immediately rejects is any promise in the collection rejects: the so-called all-or-nothing pattern.

LESSON 6 (Advanced)

And if your use case calls for you to wait and inspect the individual settled value of each promise in the collection, use .settle().

This is the opposite of the all-or-nothing pattern, or maybe the inverse, or the converse, or whatever...

LESSON 6 (Advanced)

This is trivial to do using Node's fs module, but...

It would be ideal if fs was promise based, instead of being callback based.

Turns out Bluebird makes that a 'snap' to accomplish with promisifyAll().

Sample 6 accepts a command-line argument that specifies the number of text files to create in the current directory.

LESSON 6 (Advanced)

PromisifyAll() wraps a library's functions in a promise-based api, renaming them as function_nameAsync.

Thus, instead of fs.writeFile, we call fs.writeFileAsync, and interact with it using the standard promise api.

Inspect and run Sample6:

node index-6.js  --count [int]

or    npm run sample6

LESSON 7 (Advanced)

More about Bluebird's promisifyAll feature

LESSON 7 (Advanced)

You can apply Bluebird's promisifyAll feature t a lot more than just Node modules.

Potentially any library that uses callbacks can be wrapped in a promise api, although you might have to dig a little to find the exact function set to wrap.

LESSON 7 (Advanced)

Sample 7 and Sample7b demonstrate promisifying the Github api used in earlier samples.

Sample 7 shows the 'success' case, while Sample 7b shows the 'reject' case.

Inspect and run Sample7 and Sample7b

node index-7.js  --id [githubUserName]

or    npm run sample7

LESSON 8 (Advanced)

Attaching handlers after the fact

LESSON 8 (Advanced)

Promises have the interesting behavior of settling once, and only once.  This gives rise to another interesting aspect.

If you attach a success or reject handler after the promise has settled, you immediately receive the settled value in the appropriate handler.

LESSON 8 (Advanced)

The benefit of this pattern is subtle, and depends on app architecture, but you can imagine something like this:

1. Visitor reaches home page.

​2. While authentication happens, optimistically GET (async) other data she is likely to want/need.

3. When she routes to the page that needs it, attach a success handler; boom - 'instant' data retrieval. 

LESSON 8 (Advanced)

Sample 8 uses the same Github api call, but 'attaches' a handler 5 seconds after the fact.

(For demo-purposes only, setTimeout is used to implement the "after the fact" aspect of this scenario. Don't do this for real.)

Inspect and run Sample8

node index-8.js  --id [githubUserName]

or    npm run sample8

finished

Please report any issues to the project on Github.

Thanks!

Made with Slides.com