10

I am working on a javascript project that needs to get some data and process it, but I am having trouble with the asynchronous nature of JavaScript. What I want to be able to do is something like the following.

//The set of functions that I want to call in order
function getData() {
    //gets the data
}

function parseData() {
    //does some stuff with the data
}

function validate() {
    //validates the data
}

//The function that orchestrates these calls 
function runner() {
    getData();
    parseData();
    validate();
}

Here I want each function to wait for completion before going on to the next call, as I am running into the situation where the program attempts to validate the data before it has been retrieved. However, I also want to be able to return a value from these functions for testing, so I can't have these functions return a boolean value to check completion. How can I make javascript wait on the function to run to completion before moving on to the next call?

Nick P
  • 347
  • 1
  • 4
  • 13
  • *"...I am having trouble with the asynchronous nature of JavaScript..."* JavaScript, itself, has basically no asynchronous nature. It's frequently used in *environments* that do, like the browser, or NodeJS. – T.J. Crowder Jun 08 '16 at 16:38
  • 1
    I'd suggest reading up on [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). – Mike Cluck Jun 08 '16 at 16:39
  • Promisify all three and use Promise.all to wait for all three then. Basic stuff that is possible with promises. – Wiktor Zychla Jun 08 '16 at 16:39
  • all you need is the callback function, or the Promise – Hui-Yu Lee Jun 08 '16 at 16:39
  • See [`[javascript] asynchronous functions series`](https://stackoverflow.com/search?q=%5Bjavascript%5D+asynchronous+functions+series) – Felix Kling Jun 08 '16 at 16:43

3 Answers3

17

Use promises:

//The set of functions that I want to call in order
function getData(initialData) {
  //gets the data
  return new Promise(function (resolve, reject) {
    resolve('Hello World!')
  })
}

function parseData(dataFromGetDataFunction) {
  //does some stuff with the data
  return new Promise(function (resolve, reject) {
    resolve('Hello World!')
  })
}

function validate(dataFromParseDataFunction) {
  //validates the data
  return new Promise(function (resolve, reject) {
    resolve('Hello World!')
  })
}

//The function that orchestrates these calls 
function runner(initialData) {
    return getData(initialData)
        .then(parseData)
        .then(validate)
}

runner('Hello World!').then(function (dataFromValidateFunction) {
    console.log(dataFromValidateFunction);
})

Not only are they easy to grasp, it makes total sense from a code readability stand point. Read more about them here. If you are in a browser environment, I recommend this polyfill.

m-a-r-c-e-l-i-n-o
  • 2,622
  • 18
  • 26
  • When I make each one of these use promises, can I have them return something like a string as well, or does the promise have to be the only thing that the function returns? I want to be able to use that string to check the output versus an expected output. – Nick P Jun 08 '16 at 16:48
  • It depends on your use case. If you want to chain them (like you are suggesting in your post), you'll need to return a promise because there needs to be a "then" method for the next function to be called upon. However, if all you cared about was knowing when all three functions are done, you could return a promise or some other value, and catch them all with a method called "[Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)", but they will not follow a sequence. – m-a-r-c-e-l-i-n-o Jun 08 '16 at 16:54
  • They have to follow a sequence, but I also need to be able to get a value out of them. Also, are promises built into javascript or do I need to get this from somewhere? – Nick P Jun 08 '16 at 16:58
  • I suppose that I could set a variable to the value that I need, but that seems less than ideal, doesn't it? – Nick P Jun 08 '16 at 17:00
  • You'll be able to get the values, I just edited the post to demonstrate that, take a look at the arguments on the functions to see how the data flows. Promise is built into most [modern browsers](http://caniuse.com/#feat=promises), for legacy support, use the "[polyfill](https://github.com/stefanpenner/es6-promise)" I suggested in the post. – m-a-r-c-e-l-i-n-o Jun 08 '16 at 17:04
  • So, can I then get the value that is returned by each of these functions and do stuff with it in the middle? Like, if I wanted to call these in a test class, could I test whatever that returns and then continue? – Nick P Jun 08 '16 at 17:08
  • Of course! That's why they are so powerful. You could do something with the data that comes in and push it to the next function when you are ready. So inside the promise callback, instead of doing "resolve('Hello World!')", you would do "resolve(variableContainingDataIHaveManipulated)". – m-a-r-c-e-l-i-n-o Jun 08 '16 at 17:09
  • Please consider marking this answer as correct (by clicking on the grey check mark on the top-left of this answer) if it addressed your concerns. – m-a-r-c-e-l-i-n-o Jun 08 '16 at 17:36
7

The code you've quoted will run synchronously. JavaScript function calls are synchronous.

So I'm going to assume that getData, parseData, and/or validate involve asynchronous operations (such as using ajax in a browser, or readFile in NodeJS). If so, you basically have two options, both of which involve callbacks.

The first is to just have those functions accept callbacks they'll call when done, for instance:

function getData(callback) {
    someAsyncOperation(function() {
        // Async is done now, call the callback with the data
        callback(/*...some data...*/);
    });
}

you'd use that like this:

getData(function(data) {
    // Got the data, do the next thing
});

The problem with callbacks is that they're hard to compose and have fairly brittle semantics. So promises were invented to give them better semantics. In ES2015 (aka "ES6") or with a decent promises library, that would look something like this:

function getData(callback) {
    return someAsyncOperation();
}

or if someAsyncOperation is not promise-enabled, then:

function getData(callback) {
    return new Promise(function(resolve, reject) {
        someAsyncOperation(function() {
            // Async is done now, call the callback with the data
            resolve(/*...some data...*/);
            // Or if it failed, call `reject` instead
        });
    });
}

Doesn't seem to do much for you, but one of the key things is composability; your final function ends up looking like this:

function runner() {
    return getData()
        .then(parseData) // Yes, there really aren't () on parseData...
        .then(validate); // ...or validate
}

usage:

runner()
    .then(function(result) {
         // It worked, use the result
    })
    .catch(function(error) {
         // It failed
    });

Here's an example; it will only work on a fairly recent browser that supports Promise and ES2015 arrow functions, because I was lazy and wrote it with arrow functions and didn't include a Promise lib:

"use strict";

function getData() {
    // Return a promise
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // Let's fail a third of the time
            if (Math.random() < 0.33) {
                reject("getData failed");
            } else {
                resolve('{"msg":"This is the message"}');
            }
        }, Math.random() * 100);
    });
}

function parseData(data) {
    // Note that this function is synchronous
    return JSON.parse(data);
}

function validate(data) {
    // Let's assume validation is synchronous too
    // Let's also assume it fails half the time
    if (!data || !data.msg || Math.random() < 0.5) {
        throw new Error("validation failed");
    }
    // It's fine
    return data;
}

function runner() {
    return getData()
        .then(parseData)
        .then(validate);
}

document.getElementById("the-button").addEventListener(
    "click",
    function() {
        runner()
            .then(data => {
                console.log("All good! msg: " + data.msg);
            })
            .catch(error => {
                console.error("Failed: ", error && error.message || error);
            });
    },
  false
);
<input type="button" id="the-button" value="Click to test">
(you can test more than once)
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

You should change each function to return a Promise, which will allow your final function to become:

function runner() {
    return Promise.try(getData).then(parseData).then(validate);
}

To do that, the body of each function should be wrapped in a new promise, like:

function getData() {
  return new Promise(function (res, rej) {
    var req = new AjaxRequest(...); // make the request
    req.onSuccess = function (data) {
      res(data);
    };
  });
}

This is a very cursory example of how promises might work. For more reading, check out:

ssube
  • 47,010
  • 7
  • 103
  • 140
  • So promise is not a library or something that I need to reference? It is built into javascript? – Nick P Jun 08 '16 at 16:51
  • Promise is a standardized API, with an implementation built into JS (the MDN link in my answer). It's supported [in most browsers](http://caniuse.com/#feat=promises) but not IE, so Bluebird provides a library version with a few extra features (and better performance). – ssube Jun 08 '16 at 16:54
  • Awesome that it is a built in thing. Thanks for the clarification there. – Nick P Jun 08 '16 at 16:59