skip to content

JavaScript: Using a Promise to make sequential Ajax requests

By definition Ajax is supposed to be 'asynchronous' meaning that if a series of Ajax requests are initiated, they occur in parallel with the responses being processed as they arrive back to the browser:

But sometimes the order of processing can be important, or you may have a script that needs to complete an action (setting a cookie, writing to the database, etc.) before being called again:

This can be achieved by wrapping each Ajax request up as a Promise with each subsequent request waiting for the previous to be 'resolved'.

Working Demonstration

In this simple demonstration, a click on one of the three coloured boxes to the left will trigger an Ajax request that in turn changes the colour of the larger box after a delay of one second.

The parallel button will send three requests at the same time, while sequential wraps each request in a Promise with an instruction to await resolution.

  • red
  • green
  • blue
  • parallel
  • sequential

We are using Ajax here only for demonstrative purposes - the same effect could easily be achieved using JavaScript setTimeout. The point is to have an asynchronous request which involves a short delay.

JavaScript Code

Here you have the entire codebase for the above demonstration:

<script src="/scripts/AjaxRequestXML.js"></script> <script> const callAjax = function(method, params, callback) { let AjaxTarget = "/scripts/" + method.toLowerCase() + ".xml"; (new AjaxRequestXML()).post(AjaxTarget, params, callback); }; const RequestColourChange = function(el) { let params = {}; for(let d in el.dataset) { params[d] = el.dataset[d]; } callAjax("ajax-async-await", params); }; /* trigger single Ajax request */ const ChangeColourClickHandler = function(e) { RequestColourChange(e.target); }; /* trigger parallel Ajax requests */ const parallelColourChange = function() { document.querySelectorAll("#demo > div:first-child li[data-color]").forEach(function(nextButton) { RequestColourChange(nextButton); }); }; /* trigger sequential Ajax requests */ const RequestColourChangePromise = function(el) { return new Promise(function(resolve) { const ChangeColourCallback = function() { resolve(); }; let params = {}; for(let d in el.dataset) { params[d] = el.dataset[d]; } callAjax("ajax-async-await", params, ChangeColourCallback); }); }; const sequentialColourChange = async function() { let colourButtons = document.querySelectorAll("#demo > div:first-child li[data-color]"); for(const button of colourButtons) { /* cannot use forEach here! */ await RequestColourChangePromise(button); } }; /* initiate all button click handlers */ document.querySelectorAll("#demo > div:first-child li").forEach(function(nextButton) { if(nextButton.dataset.color) { nextButton.addEventListener("click", ChangeColourClickHandler); } else if(nextButton.dataset.method) { nextButton.addEventListener("click", function(e) { switch(nextButton.dataset.method) { case "parallel": parallelColourChange(); break; case "sequential": sequentialColourChange(); break; } }); } }); </script>

expand code box

Making a simple Ajax request

You can see that in most cases we invoke RequestColourChange to generate the Ajax request that changes the colour background. This should be mostly self-explanatory:

const RequestColourChange = el => { let params = {}; for(let d in el.dataset) { params[d] = el.dataset[d]; } callAjax("ajax-async-await", params); };

For the parallel case we loop through the coloured buttons and trigger RequestColourChange once for each. These requests take place concurrently resulting in a race condition as to which colour background you end up with.

If you're curious about the contents of the Ajax XML response you can monitor them in your inspector under Sources > XHRs.

Using a Promise to sequentialise requests

To achieve sequentialisation we need to insert into our code an async function that can await for a Promise to resolve. Sounds simple enough.

In the code we replace RequestColourChange with a new method RequestColourChangePromise which does more or less the same thing, only creating and returning a Promise object.

Internal to this Promise object our Ajax call now specifies a callback function to be triggered when the response comes back from the web server. By calling resolve() in the callback function the state of the Promise changes from 'pending' to 'fulfilled':

const RequestColourChangePromise = el => { return new Promise(resolve => { const ChangeColourCallback = () => resolve(); let params = {}; for(let d in el.dataset) { params[d] = el.dataset[d]; } callAjax("ajax-async-await", params, ChangeColourCallback); }); };

Meanwhile we have an async function sequentialColourChange which calls the RequestColourChangePromise function using the await operand which halts execution until the returned Promise is fulfilled (or rejected):

const sequentialColourChange = async() => { let colourButtons = document.querySelectorAll("#demo > div:first-child li[data-color]"); for(const button of colourButtons) { /* cannot use forEach here! */ await RequestColourChangePromise(button); } };

The await operator is used to wait for a Promise and get its fulfillment value. It can only be used inside an async function or a JavaScript module -- mozilla.

The catch here is that if you try to put the await calls inside a forEach loop - as seen elsewhere in the code - it will not work because forEach runs each loop as a callback rather than all a part of a single function.

< JavaScript

Post your comment or question
top