How to synchronously call a set of functions in javascript
Asked Answered
B

3

12

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?

Bathsheba answered 8/6, 2016 at 16:37 Comment(5)
"...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.Gerladina
I'd suggest reading up on Promises.Glyceric
Promisify all three and use Promise.all to wait for all three then. Basic stuff that is possible with promises.Swashbuckling
all you need is the callback function, or the PromiseDevindevina
See [javascript] asynchronous functions seriesLobectomy
F
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.

Fanya answered 8/6, 2016 at 16:42 Comment(8)
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.Bathsheba
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", but they will not follow a sequence.Fanya
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?Bathsheba
I suppose that I could set a variable to the value that I need, but that seems less than ideal, doesn't it?Bathsheba
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, for legacy support, use the "polyfill" I suggested in the post.Fanya
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?Bathsheba
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)".Fanya
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.Fanya
G
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)
Gerladina answered 8/6, 2016 at 16:44 Comment(0)
B
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:

Biophysics answered 8/6, 2016 at 16:41 Comment(3)
So promise is not a library or something that I need to reference? It is built into javascript?Bathsheba
Promise is a standardized API, with an implementation built into JS (the MDN link in my answer). It's supported in most browsers but not IE, so Bluebird provides a library version with a few extra features (and better performance).Biophysics
Awesome that it is a built in thing. Thanks for the clarification there.Bathsheba

© 2022 - 2024 — McMap. All rights reserved.