[javascript] Call An Asynchronous Javascript Function Synchronously

First, this is a very specific case of doing it the wrong way on-purpose to retrofit an asynchronous call into a very synchronous codebase that is many thousands of lines long and time doesn't currently afford the ability to make the changes to "do it right." It hurts every fiber of my being, but reality and ideals often do not mesh. I know this sucks.

OK, that out of the way, how do I make it so that I could:

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

  // block here and return data when the callback is finished
  return data;
}

The examples (or lack thereof) all use libraries and/or compilers, both of which are not viable for this solution. I need a concrete example of how to make it block (e.g. NOT leave the doSomething function until the callback is called) WITHOUT freezing the UI. If such a thing is possible in JS.

This question is related to javascript asynchronous

The answer is


The idea that you hope to achieve can be made possible if you tweak the requirement a little bit

The below code is possible if your runtime supports the ES6 specification.

More about async functions

async function myAsynchronousCall(param1) {
    // logic for myAsynchronous call
    return d;
}

function doSomething() {

  var data = await myAsynchronousCall(param1); //'blocks' here until the async call is finished
  return data;
}

There is one nice workaround at http://taskjs.org/

It uses generators which are new to javascript. So it's currently not implemented by most browsers. I tested it in firefox, and for me it is nice way to wrap asynchronous function.

Here is example code from project GitHub

var { Deferred } = task;

spawn(function() {
    out.innerHTML = "reading...\n";
    try {
        var d = yield read("read.html");
        alert(d.responseText.length);
    } catch (e) {
        e.stack.split(/\n/).forEach(function(line) { console.log(line) });
        console.log("");
        out.innerHTML = "error: " + e;
    }

});

function read(url, method) {
    method = method || "GET";
    var xhr = new XMLHttpRequest();
    var deferred = new Deferred();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status >= 400) {
                var e = new Error(xhr.statusText);
                e.status = xhr.status;
                deferred.reject(e);
            } else {
                deferred.resolve({
                    responseText: xhr.responseText
                });
            }
        }
    };
    xhr.open(method, url, true);
    xhr.send();
    return deferred.promise;
}

You can also convert it into callbacks.

function thirdPartyFoo(callback) {    
  callback("Hello World");    
}

function foo() {    
  var fooVariable;

  thirdPartyFoo(function(data) {
    fooVariable = data;
  });

  return fooVariable;
}

var temp = foo();  
console.log(temp);

One thing people might not consider: If you control the async function (which other pieces of code depend on), AND the codepath it would take is not necessarily asynchronous, you can make it synchronous (without breaking those other pieces of code) by creating an optional parameter.

Currently:

async function myFunc(args_etcetc) {
    // you wrote this
    return 'stuff';
}

(async function main() {
    var result = await myFunc('argsetcetc');
    console.log('async result:' result);
})()

Consider:

function myFunc(args_etcetc, opts={}) {
    /*
        param opts :: {sync:Boolean} -- whether to return a Promise or not
    */
    var {sync=false} = opts;
    if (sync===true)
        return 'stuff';
    else
        return new Promise((RETURN,REJECT)=> {
            RETURN('stuff');
        });
}


// async code still works just like before:
(async function main() {
    var result = await myFunc('argsetcetc');
    console.log('async result:', result);
})();
// prints: 'stuff'

// new sync code works, if you specify sync mode:
(function main() {
    var result = myFunc('argsetcetc', {sync:true});
    console.log('sync result:', result);
})();
// prints: 'stuff'

Of course this doesn't work if the async function relies on inherently async operations (network requests, etc.), in which case the endeavor is futile (without effectively waiting idle-spinning for no reason).

Also this is fairly ugly to return either a value or a Promise depending on the options passed in.

("Why would I have written an async function if it didn't use async constructs?" one might ask? Perhaps some modalities/parameters of the function require asynchronicity and others don't, and due to code duplication you wanted a monolithic block rather than separate modular chunks of code in different functions... For example perhaps the argument is either localDatabase (which doesn't require await) or remoteDatabase (which does). Then you could runtime error if you try to do {sync:true} on the remote database. Perhaps this scenario is indicative of another problem, but there you go.)


What you want is actually possible now. If you can run the asynchronous code in a service worker, and the synchronous code in a web worker, then you can have the web worker send a synchronous XHR to the service worker, and while the service worker does the async things, the web worker's thread will wait. This is not a great approach, but it could work.


Take a look at JQuery Promises:

http://api.jquery.com/promise/

http://api.jquery.com/jQuery.when/

http://api.jquery.com/deferred.promise/

Refactor the code:


    var dfd = new jQuery.Deferred();


    function callBack(data) {
       dfd.notify(data);
    }

    // do the async call.
    myAsynchronousCall(param1, callBack);

    function doSomething(data) {
     // do stuff with data...
    }

    $.when(dfd).then(doSomething);



Async functions, a feature in ES2017, make async code look sync by using promises (a particular form of async code) and the await keyword. Also notice in the code examples below the keyword async in front of the function keyword that signifies an async/await function. The await keyword won't work without being in a function pre-fixed with the async keyword. Since currently there is no exception to this that means no top level awaits will work (top level awaits meaning an await outside of any function). Though there is a proposal for top-level await.

ES2017 was ratified (i.e. finalized) as the standard for JavaScript on June 27th, 2017. Async await may already work in your browser, but if not you can still use the functionality using a javascript transpiler like babel or traceur. Chrome 55 has full support of async functions. So if you have a newer browser you may be able to try out the code below.

See kangax's es2017 compatibility table for browser compatibility.

Here's an example async await function called doAsync which takes three one second pauses and prints the time difference after each pause from the start time:

_x000D_
_x000D_
function timeoutPromise (time) {_x000D_
  return new Promise(function (resolve) {_x000D_
    setTimeout(function () {_x000D_
      resolve(Date.now());_x000D_
    }, time)_x000D_
  })_x000D_
}_x000D_
_x000D_
function doSomethingAsync () {_x000D_
  return timeoutPromise(1000);_x000D_
}_x000D_
_x000D_
async function doAsync () {_x000D_
  var start = Date.now(), time;_x000D_
  console.log(0);_x000D_
  time = await doSomethingAsync();_x000D_
  console.log(time - start);_x000D_
  time = await doSomethingAsync();_x000D_
  console.log(time - start);_x000D_
  time = await doSomethingAsync();_x000D_
  console.log(time - start);_x000D_
}_x000D_
_x000D_
doAsync();
_x000D_
_x000D_
_x000D_

When the await keyword is placed before a promise value (in this case the promise value is the value returned by the function doSomethingAsync) the await keyword will pause execution of the function call, but it won't pause any other functions and it will continue executing other code until the promise resolves. After the promise resolves it will unwrap the value of the promise and you can think of the await and promise expression as now being replaced by that unwrapped value.

So, since await just pauses waits for then unwraps a value before executing the rest of the line you can use it in for loops and inside function calls like in the below example which collects time differences awaited in an array and prints out the array.

_x000D_
_x000D_
function timeoutPromise (time) {_x000D_
  return new Promise(function (resolve) {_x000D_
    setTimeout(function () {_x000D_
      resolve(Date.now());_x000D_
    }, time)_x000D_
  })_x000D_
}_x000D_
_x000D_
function doSomethingAsync () {_x000D_
  return timeoutPromise(1000);_x000D_
}_x000D_
_x000D_
// this calls each promise returning function one after the other_x000D_
async function doAsync () {_x000D_
  var response = [];_x000D_
  var start = Date.now();_x000D_
  // each index is a promise returning function_x000D_
  var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync];_x000D_
  for(var i = 0; i < promiseFuncs.length; ++i) {_x000D_
    var promiseFunc = promiseFuncs[i];_x000D_
    response.push(await promiseFunc() - start);_x000D_
    console.log(response);_x000D_
  }_x000D_
  // do something with response which is an array of values that were from resolved promises._x000D_
  return response_x000D_
}_x000D_
_x000D_
doAsync().then(function (response) {_x000D_
  console.log(response)_x000D_
})
_x000D_
_x000D_
_x000D_

The async function itself returns a promise so you can use that as a promise with chaining like I do above or within another async await function.

The function above would wait for each response before sending another request if you would like to send the requests concurrently you can use Promise.all.

_x000D_
_x000D_
// no change_x000D_
function timeoutPromise (time) {_x000D_
  return new Promise(function (resolve) {_x000D_
    setTimeout(function () {_x000D_
      resolve(Date.now());_x000D_
    }, time)_x000D_
  })_x000D_
}_x000D_
_x000D_
// no change_x000D_
function doSomethingAsync () {_x000D_
  return timeoutPromise(1000);_x000D_
}_x000D_
_x000D_
// this function calls the async promise returning functions all at around the same time_x000D_
async function doAsync () {_x000D_
  var start = Date.now();_x000D_
  // we are now using promise all to await all promises to settle_x000D_
  var responses = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);_x000D_
  return responses.map(x=>x-start);_x000D_
}_x000D_
_x000D_
// no change_x000D_
doAsync().then(function (response) {_x000D_
  console.log(response)_x000D_
})
_x000D_
_x000D_
_x000D_

If the promise possibly rejects you can wrap it in a try catch or skip the try catch and let the error propagate to the async/await functions catch call. You should be careful not to leave promise errors unhandled especially in Node.js. Below are some examples that show off how errors work.

_x000D_
_x000D_
function timeoutReject (time) {_x000D_
  return new Promise(function (resolve, reject) {_x000D_
    setTimeout(function () {_x000D_
      reject(new Error("OOPS well you got an error at TIMESTAMP: " + Date.now()));_x000D_
    }, time)_x000D_
  })_x000D_
}_x000D_
_x000D_
function doErrorAsync () {_x000D_
  return timeoutReject(1000);_x000D_
}_x000D_
_x000D_
var log = (...args)=>console.log(...args);_x000D_
var logErr = (...args)=>console.error(...args);_x000D_
_x000D_
async function unpropogatedError () {_x000D_
  // promise is not awaited or returned so it does not propogate the error_x000D_
  doErrorAsync();_x000D_
  return "finished unpropogatedError successfully";_x000D_
}_x000D_
_x000D_
unpropogatedError().then(log).catch(logErr)_x000D_
_x000D_
async function handledError () {_x000D_
  var start = Date.now();_x000D_
  try {_x000D_
    console.log((await doErrorAsync()) - start);_x000D_
    console.log("past error");_x000D_
  } catch (e) {_x000D_
    console.log("in catch we handled the error");_x000D_
  }_x000D_
  _x000D_
  return "finished handledError successfully";_x000D_
}_x000D_
_x000D_
handledError().then(log).catch(logErr)_x000D_
_x000D_
// example of how error propogates to chained catch method_x000D_
async function propogatedError () {_x000D_
  var start = Date.now();_x000D_
  var time = await doErrorAsync() - start;_x000D_
  console.log(time - start);_x000D_
  return "finished propogatedError successfully";_x000D_
}_x000D_
_x000D_
// this is what prints propogatedError's error._x000D_
propogatedError().then(log).catch(logErr)
_x000D_
_x000D_
_x000D_

If you go here you can see the finished proposals for upcoming ECMAScript versions.

An alternative to this that can be used with just ES2015 (ES6) is to use a special function which wraps a generator function. Generator functions have a yield keyword which may be used to replicate the await keyword with a surrounding function. The yield keyword and generator function are a lot more general purpose and can do many more things then just what the async await function does. If you want a generator function wrapper that can be used to replicate async await I would check out co.js. By the way co's function much like async await functions return a promise. Honestly though at this point browser compatibility is about the same for both generator functions and async functions so if you just want the async await functionality you should use Async functions without co.js.

Browser support is actually pretty good now for Async functions (as of 2017) in all major current browsers (Chrome, Safari, and Edge) except IE.


You can force asynchronous JavaScript in NodeJS to be synchronous with sync-rpc.

It will definitely freeze your UI though, so I'm still a naysayer when it comes to whether what it's possible to take the shortcut you need to take. It's not possible to suspend the One And Only Thread in JavaScript, even if NodeJS lets you block it sometimes. No callbacks, events, anything asynchronous at all will be able to process until your promise resolves. So unless you the reader have an unavoidable situation like the OP (or, in my case, are writing a glorified shell script with no callbacks, events, etc.), DO NOT DO THIS!

But here's how you can do this:

./calling-file.js

var createClient = require('sync-rpc');
var mySynchronousCall = createClient(require.resolve('./my-asynchronous-call'), 'init data');

var param1 = 'test data'
var data = mySynchronousCall(param1);
console.log(data); // prints: received "test data" after "init data"

./my-asynchronous-call.js

function init(initData) {
  return function(param1) {
    // Return a promise here and the resulting rpc client will be synchronous
    return Promise.resolve('received "' + param1 + '" after "' + initData + '"');
  };
}
module.exports = init;

LIMITATIONS:

These are both a consequence of how sync-rpc is implemented, which is by abusing require('child_process').spawnSync:

  1. This will not work in the browser.
  2. The arguments to your function must be serializable. Your arguments will pass in and out of JSON.stringify, so functions and non-enumerable properties like prototype chains will be lost.