Question

I'm using a jQuery library called bootbox

bootbox.dialog({
    title: "Group",
    buttons: {
        success: {
            label: "OK",
            className: "btn-success",
            callback: function () {
                postForm();
            }
        }
    }
});

function postForm() {        
    $.ajax({
        type: "POST",
        url: $("#add-group").val(),
        data: $("#form").serialize(),
        success: function (data) {
            return true;
        },
        error: function (XMLHttpRequest, textStatus, errorThrown) {
            return false;
        }
    });
}

When I click on the "OK" button, unless I return a false value like this:

callback: function () {
    return false;
}

the dialog will close.

In the callback function, I'm calling postForm which is a function that makes an AJAX call to my server side to perform some operation. Depending on the outcome of that operation, I would like to keep the dialog still open.

But because it's an AJAX call, and the result takes a while to come back, the dialog closes immediately after postForm() regardless of my result.

How can I tell javascript to await for the result of the ajax call?

It'll be great if I can do something like this:

callback: function () {
    var result = await postForm();
    return result;
}
Was it helpful?

Solution

I think Promises is exactly what you are asking.

.promise()

Return a Promise object to observe when all actions of a certain type bound to the collection, queued or not, have finished. e.g.

var div = $( "<div>" );

div.promise().done(function( arg1 ) {
  // Will fire right away and alert "true"
  alert( this === div && arg1 === div );
});

For more info refer : https://api.jquery.com/promise/

Deferred Promise is closer to async behaviour:

deferred.promise()

The deferred.promise() method allows an asynchronous function to prevent other code from interfering with the progress or status of its internal request. The Promise exposes only the Deferred methods needed to attach additional handlers or determine the state (then, done, fail, always, pipe, progress, and state), but not ones that change the state (resolve, reject, notify, resolveWith, rejectWith, and notifyWith).

If target is provided, deferred.promise() will attach the methods onto it and then return this object rather than create a new one. This can be useful to attach the Promise behavior to an object that already exists.

If you are creating a Deferred, keep a reference to the Deferred so that it can be resolved or rejected at some point. Return only the Promise object via deferred.promise() so other code can register callbacks or inspect the current state.

Example:

function asyncEvent() {
  var dfd = new jQuery.Deferred();

  // Resolve after a random interval
  setTimeout(function() {
    dfd.resolve( "hurray" );
  }, Math.floor( 400 + Math.random() * 2000 ) );

  // Reject after a random interval
  setTimeout(function() {
    dfd.reject( "sorry" );
  }, Math.floor( 400 + Math.random() * 2000 ) );

  // Show a "working..." message every half-second
  setTimeout(function working() {
    if ( dfd.state() === "pending" ) {
      dfd.notify( "working... " );
      setTimeout( working, 500 );
    }
  }, 1 );

  // Return the Promise so caller can't change the Deferred
  return dfd.promise();
}

// Attach a done, fail, and progress handler for the asyncEvent
$.when( asyncEvent() ).then(
  function( status ) {
    alert( status + ", things are going well" );
  },
  function( status ) {
    alert( status + ", you fail this time" );
  },
  function( status ) {
    $( "body" ).append( status );
  }
);

For more information, see the documentation for Deferred object: http://api.jquery.com/category/deferred-object/

jQuery.when()

Provides a way to execute callback functions based on one or more objects, usually Deferred objects that represent asynchronous events.Example:

$.when( $.ajax( "test.aspx" ) ).then(function( data, textStatus, jqXHR ) {
  alert( jqXHR.status ); // Alerts 200
});

OTHER TIPS

JavaScript does not (currently) have a language equivalent of async/await. There are various promise libraries available for JavaScript that give you a rough equivalent of the Task type. This is a good step above raw callbacks, but you still end up with awkward nesting or callback spaghetti in all but the simplest scenarios.

JavaScript ECMAScript 6 ("Harmony") is expected to include generators. ES6 is on track to become official later this year, but it will probably be some time after that before you can safely assume that your users' browsers support generators.

By combining generators with promises, you can achieve a true async/await equivalent.

With the advent of ES2017, the answer to this question is async/await.
It's THE JS solution for “callback hell”. Promise is the equivalent to System.Threading.Tasks.Task. You can await a Promise in an async function.
Unlike in C#, there is no way to call an async-function in a synchronous function.
So you can await a Promise in an async function, and ONLY in an async-function.

async function foo()
{
     return 123;
}

let result = await foo();
console.log(result)

You can use TypeScript or babel to transpile async/await back to ES5 (while IE11 exists).
There's a promise polyfill for IE11.
See ECMA-draft 262 or MDN for closer information.

A good example of promise is the FETCH api.
The fetch-API is good for async/await ajax requests.
Why ? Because if you have to promisify XmlHttpRequest, it looks like this:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

    <meta http-equiv="cache-control" content="max-age=0" />
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="expires" content="0" />
    <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
    <meta http-equiv="pragma" content="no-cache" />

    <meta charset="utf-8" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

    <meta http-equiv="Content-Language" content="en" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />

    <meta name="google" value="notranslate" />


    <!--
    <meta name="author" content="name" />
    <meta name="description" content="description here" />
    <meta name="keywords" content="keywords,here" />

    <link rel="shortcut icon" href="favicon.ico" type="image/vnd.microsoft.icon" />
    <link rel="stylesheet" href="stylesheet.css" type="text/css" />
    -->

    <title>Title</title>

    <style type="text/css" media="all">
        body
        {
            background-color: #0c70b4;
            color: #546775;
            font: normal 400 18px "PT Sans", sans-serif;
            -webkit-font-smoothing: antialiased;
        }
    </style>


    <script type="text/javascript">
        <!-- 
        // http://localhost:57566/foobar/ajax/json.ashx







        var ajax = {};
        ajax.x = function () {
            if (typeof XMLHttpRequest !== 'undefined') {
                return new XMLHttpRequest();
            }
            var versions = [
                "MSXML2.XmlHttp.6.0",
                "MSXML2.XmlHttp.5.0",
                "MSXML2.XmlHttp.4.0",
                "MSXML2.XmlHttp.3.0",
                "MSXML2.XmlHttp.2.0",
                "Microsoft.XmlHttp"
            ];

            var xhr;
            for (var i = 0; i < versions.length; i++) {
                try {
                    xhr = new ActiveXObject(versions[i]);
                    break;
                } catch (e) {
                }
            }
            return xhr;
        };

        ajax.send = function (url, callback, method, data, async) {
            if (async === undefined) 
            {
                async = true;
            }

            var x = ajax.x();
            x.open(method, url, async);
            x.onreadystatechange = function () {
                if (x.readyState == 4) {
                    callback(x.responseText)
                }
            };
            if (method == 'POST') {
                x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
            }
            x.send(data)
        };

        ajax.get = function (url, data, callback, async) {
            var query = [];
            for (var key in data) {
                query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
            }
            ajax.send(url + (query.length ? '?' + query.join('&') : ''), callback, 'GET', null, async)
        };

        ajax.post = function (url, data, callback, async) {
            var query = [];
            for (var key in data) {
                query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
            }
            ajax.send(url, callback, 'POST', query.join('&'), async)
        };


        ///////////



        function testAjaxCall() {
            ajax.get("./ajax/json.ashx", null, function (bError, strMessage, iStatus)
                {
                    console.log("args:", arguments);

                    console.log("Error:", bError);
                    console.log("Message:", strMessage);
                    console.log("Status:", iStatus);
                }
                , true
            );

        }
        -->
    </script>

</head>
<body>

    <script type="text/javascript">

        function ajaxGet(url, data)
        {
            var result;

            return new Promise(function (resolve, reject)
                {

                    ajax.get(url, data, function (bError, strMessage, iStatus)
                        {

                            // console.log("args:", arguments);

                            // console.log("Error:", bError);
                            // console.log("Message:", strMessage);
                            // console.log("Status:", iStatus);

                            result = bError;
                            resolve(result);
                        }
                        ,true
                    );

                }
            );

        }


        async function main()
        {
            var ajaxResult = await ajaxGet("./ajax/json.ashx");
            console.log("ajaxResult: ", ajaxResult);
        }

        async function lol() 
        { 
            var res = null;

            var myPromise = new Promise(function (resolve, reject)
            {
                // Standard AJAX request setup and load.
                var request = new XMLHttpRequest();

                // Request a user's comment from our fake blog.
                request.open('GET', 'https://localhost:57566/ajax/json.ashx');

                /*
                // Set function to call when resource is loaded.
                // Onload same as onreadystatechange - onload added with XHR2
                request.onload = function ()
                {
                    // internal server error/404
                    if (request.status === 200)
                    {
                        res = request.response;
                        // console.log(request.response);
                        console.log("onload- resolving promise");
                        resolve(request.response);
                    } else
                    {
                        console.log("onload- rejectinv promise");
                        reject('Page loaded, but status not OK.');
                    }
                };
                */


                request.onreadystatechange = function ()
                {
                    console.log("readystate:", request.readyState);
                    console.log("status:", request.status)

                    if (request.readyState != 4) return;

                    // XMLHttpRequest.DONE = 200, 0=cancelled 304 = redirect
                    //if (!(request.status != 200 && request.status != 304 && request.status != 0))
                    if (request.status === 200)
                    {
                        console.log("successy")
                        resolve(request.responseText); // Success 
                        return;
                    }

                    if (request.status != 200 && request.status != 0 && request.status != 304)
                    {
                        console.log('HTTP error ' + request.status);
                        // reject('Page loaded, but status not OK.');
                        reject(new Error("Server error - Status NOK", "filename", "linenum666")); // Error 
                        return;
                    }

                    if (request.status === 0)
                    {
                        console.log("cancelled:", request)
                        //resolve(null); // Cancelled, HTTPS protocol error
                        return;
                    }

                    reject(new Error("Strange error", "filename", "linenum666")); // Some Error 
                };

                // Set function to call when loading fails.
                request.onerror = function ()
                {
                    // Cannot connect 
                    console.log("OMG OnError");
                    // reject('Aww, didn\'t work at all. Network connectivity issue.');
                    reject(new Error("Aww, didn\'t work at all. Network connectivity issue.", "filename", "linenum666")); // Some Error 

                };


                if (!navigator.onLine)
                {
                    console.log("No internet connection");
                    reject("No internet connection");
                }
                else
                {
                    try
                    {
                        request.send();
                    }
                    catch (ex)
                    {
                        console.log("send", ex.message, ex);
                    }
                }

            });

            return myPromise;
        }



        async function autorun()
        {
            console.clear();
            // await main();

            try
            {
                var resp = await lol();
                console.log("resp:", resp);
            }
            catch (ex)
            {
                console.log("foo", ex.message, ex);
            }



            console.log("I am here !");
        }

        if (document.addEventListener) document.addEventListener("DOMContentLoaded", autorun, false);
        else if (document.attachEvent) document.attachEvent("onreadystatechange", autorun);
        else window.onload = autorun;
    </script>

</body>
</html>

You can't. There is no equivalent of await in JS.

You will have to simulate it by returning false at the invocation of postForm and then upon execution of the callback-function from the AJAX call close the dialog.

EDIT/UPDATE: Starting with ES2017 there is async/await support - though I don't know if it works in conjunction with jQuery.

See Stefan's answer on using ES2015 async/await now adays.


Original Answer

You could consider asyncawait which effectively lets you write code as follows

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});

Instead of the following

function foo2(callback) {
    firstAsyncCall(function (err, resultA) {
        if (err) { callback(err); return; }
        secondAsyncCallUsing(resultA, function (err, resultB) {
            if (err) { callback(err); return; }
            thirdAsyncCallUsing(resultB, function (err, resultC) {
                if (err) {
                    callback(err);
                } else {
                    callback(null, doSomethingWith(resultC));
                }
            });

        });
    });
}

You can use ES6 generators and yield feature with Google Traceur Compiler, as I described here.

There is some feedback from Traceur team indicating it's a tool of the production quality. It also has experimental support for async/await.

While this doesn't answer the "what's the equivalent of C#'s await in JavaScript?" question, the code in the question can be made to work rather easily. The bootbox.dialog function returns an object, so you can adjust the code shown like so:

var dialog = bootbox.dialog({
    title: "Group",
    buttons: {
        success: {
            label: "OK",
            className: "btn-success",
            callback: function () {
                postForm();
                return false; // add this return here
            }
        }
    }
});

And then the ajax call gets adjusted to:

function postForm() {        
    $.ajax({
        type: "POST",
        url: $("#add-group").val(),
        data: $("#form").serialize(),
        success: function (data) {
            // add this call to the underlying Bootstrap modal object
            dialog.modal('hide'); 
        },
        error: function (XMLHttpRequest, textStatus, errorThrown) {
            // Maybe inject an error message into the dialog?
        }
    });
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top