Wednesday, March 9, 2016

JavaScript async: the future looks promising

In the beginning there was the Pyramids.

Okay, that was not exactly true. We actually had Ajax before that. With ajax, we also got callbacks. Some of the mighty callback pyramids were built and are still standing.

About one year ago, ECMAScript 2015 (aka ES 6) was released and brought native support for Promises to JavaScript. Does that mean that it now is possible to build smaller callback pyramids, instead of the mighty ones? Yes. We also have a standardized and more reliable way of writing async JavaScript code. We can write async code that behaves in a more predictable way when things go wrong.

Here's an example.

// the async consumer code, passing an url and a callback
get('path/to/my/serverside/api', (response) => {
// handle the response here
});


// the callback based ajax library that we use
function get(url, onSuccess, onFail) {
// ajax things here
// pass the result to the provided callback function
onSuccess(result);
}

What if the ajax library at some point would ... go crazy?

onSuccess(result);
onSuccess(result);
onSuccess(result);


The callback will be executed several times and it is out of our control. However, this problem can be solved by using promises. Wrap the ajax library in a promise function and consume the wrapper instead:


// the wrapper
function promiseGet(url) {
    
    return new Promise ((resolve, reject) => {
        get(url, resolve, reject);
    });
}


// consume the promisified version
promiseGet('path/to/my/serverside/api')
.then((response) => {
    // handle the response here
});

The promise will be resolved only once, even if the ajax library goes bananas.


The bus stop
This reminds me of when I was on the bus with the kids a couple of days ago. The little one wanted to press the shiny "bus-will-stop" button. When we got closer to our stop, the kid pressed the button and got the expected feedback (a "bus-will-stop" signal!). That was fun! So, he pressed the button again. But nothing happened. Naturally, he tried a couple of times more. Nothing happened.

Just like the "bus-will-stop" button, a promise is resolved only once. I think the bus driver (and passengers) appreciate the feature.


The async brain?
Async JavaScript code - with or without Promises - isn't always the easiest thing to understand. Promises does not really solve the mismatch between how our brains are wired and the flow of callbacks & thenable functions. We have learned how to write async code, and sometimes even understand it. But wouldn't it be cool if we could write something sequential like this?


var result = get('path/to/my/serverside/api');

console.log(result);


Of course, the code above won't work. 

I will try again, by using some of the magic of generators. A generator enable pause and continue functionality to a function. You can jump back and forth between a generator and the consumer code.


function* myGenerator() {

    var result = yield get('path/to/my/serverside/api');
    console.log(result);

}


That code won't work out of the box either. We need some help from a library. This library (the run function) will help us go back and forth between the generator and the consumer code, and resolve promises.


import run from 'async-runner';

run(function* myGenerator() {

    var result = yield get('path/to/my/serverside/api');
    console.log(result);

});


This will work. (here's the code used in this post)

Sequential async?
Okay, cool. It looks nice and sequential, at least when focusing on the rows within the function. But wait. Will people understand code with funky star functions and weird libraries? There has to be another way. I think the upcoming ECMAScript async & await feature can help us write easy to understand sequential code, without funky star functions or weird libraries.

Let's remove some code from our previous example:

import run from 'async-runner';

run(function* myGenerator() {

    var result = yield get('path/to/my/serverside/api');
    console.log(result);

});


And add some sweet futuristic async & await JavaScript syntax sugar:

async function myAsyncCode() {

    var result = await get('path/to/my/serverside/api');
    console.log(result);

};

With this, I think it actually is possible to use the words "readable" and "async" in the same sentence. Behind the scene, the async & await feature is a combination of Promises and Generators. I think the future of async programming looks very promising. Maybe we don't have to wait for it either. The transpiler Babel has already support for async & await today. Perhaps it is time to go back to the future?


here's the code used in this post

2 comments:

pdmunro said...

David, I don't know if I am doing things correctly. I just guessed some things. This is what I did. I got an error - see below.

(1) I downloaded the GitHub code at https://davidvujic.github.io/vanillajs-components/ and unzipped it.
(2) I downloaded npm and installed it.
(3) When I typed npm in the start area of Win 10, a noje.js command window appeared.
(4) I changed the directory to the one where your Github code is.
(5) I typed "npm install". (Lots of code appeared in a folder called node_modules.)
(6) I typed "npm start". I got an error message about a python server
(7) I went to the Python website and downloaded Python 3.
(7) I tried "npm start" again. I got the following error messages. Am I on the right track?
Should I have downloaded Python 2? (KEY QUESTION)

****** Error messages ******
17 error Windows_NT 10.0.15063
18 error argv "C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "start"
19 error node v6.11.2
20 error npm v3.10.10
21 error code ELIFECYCLE
22 error vanillajs-components@1.0.0 start: `python -m SimpleHTTPServer 8080`
22 error Exit status 1
23 error Failed at the vanillajs-components@1.0.0 start script 'python -m SimpleHTTPServer 8080'.
23 error Make sure you have the latest version of node.js and npm installed.
23 error If you do, this is most likely a problem with the vanillajs-components package,
23 error not with npm itself.
23 error Tell the author that this fails on your system:
23 error python -m SimpleHTTPServer 8080
23 error You can get information on how to open an issue for this project with:
23 error npm bugs vanillajs-components
23 error Or if that isn't available, you can get their info via:
23 error npm owner ls vanillajs-components
23 error There is likely additional logging output above.
24 verbose exit [ 1, true ]
**** End error messages ******

Peter

David Vujic said...

(I think this comment is about a different post in this blog: http://davidvujic.blogspot.se/2017/08/ndc-oslo-video-javascript-in-2017-you-might-not-need-a-framework.html)

Hi!

Yes, to get everything installed correctly, you need to navigate to the project folder where the "package.json" file is. That's where you should type "npm install".

The "npm start" command is intended to make it simple to run the web page in a local web server. If you are a Windows user, you could just create an IIS website instead - or install the node based "live-server", using the command "npm install --save-dev live-server".

On Linux, and also on Mac OS X (I am pretty sure), Python is already installed. But not on Windows. If you like, you could install Python 2.7 - then you can use the built in SimpleHTTPServer.

Thank you for your comment on this blog!