Queueing Javascript Promises

July 27, 2022

Often when building a server-client web application, we will encounter a situation where we want to send requests to our API in the chronological order that they occur on the client. Due to the asynchronous nature of these requests, it might not be possible to send them in the same callback for the event that triggered them. This is because we want to use the response from the previous request to craft our current one. A solution to this problem would be to implement a queue. Instead of calling the API immediately after events occur, implementing a queue ensures the latest data is sent with any request.

Let’s start by breaking down some of the terminology we’ll be using.

What is a queue?

A queue is a data structure that allows you to collect individual units of work in a pile and then take from that pile at a later time. A unit of work can be anything! It might be the data you want to operate on or a function that wraps the work you want to do. In this post we’ll be pretending to fetch data from an API as an example. 

The order in which you take work from your queue is defined by your application's needs. The most commonly used method is First In First Out (FIFO), which pretty much does what it sounds like. If you enqueue 2 items, the one you added first should be worked on (dequeued) before the second item.

Items are enqueued to the back of the queue and dequeued from the front.

What is a Promise?

“A Promise is an object representing the eventual completion ( or failure ) of an asynchronous operation” - MDN

We will use Promises to call the next item from our queue. A Promise is essentially a placeholder that says, "I don't have the result of this yet, but when it completes, I will put the result here."  Promises are one of the mechanisms that allow for asynchronous code in JavaScript. You use them when you want to do some work but don’t want to be blocked by that work.

When you create a Promise you pass one function that takes two callbacks as arguments. The first callback is used when the Promise is successful or “resolved.” The second callback is used when the Promise encounters an error or is “rejected.” Promises are always in 1 of 3 possible states; pending, fulfilled, or rejected.

You can also chain Promises by using the “then” and “catch” functions. The “then” function is called when the promise is fulfilled and the “catch” function is called when the promise is rejected.

However, the “then” callback will always execute as long as the Promise is not handled in the rejected state. We will demonstrate this caveat in the example code below.

A simplified Promise lifecycle.

How do we put this together?

We start by creating a function that generates a queue to manage our API requests. It will take two arguments that we will chain to each of the Promises in our queue’s stack. The first argument will be a function that is called when the unit of work is successfully completed. The second will be called when it fails.

Next we will store the items for our queue in an array, allowing us to order the units of work. The [.inline-snippet]items[.inline-snippet] will be placed at the end / top and we will take them from the front / bottom.

Let’s skip down to the returned function aptly named [.inline-snippet]enqueue[.inline-snippet], which is the method we will use to stack items to the top / end of our queue. This function takes one argument, a function that when invoked returns a Promise, and if we only have one item in our stack we will dequeue and invoke that function right away.

Now we will look at the dequeue function. We will start by invoking the function at the bottom / front of our queue. This will return a Promise that we can chain our onResolve and onReject callbacks that we passed when we created the queue. After that we will remove the current function that we just executed from our stack since that work is now complete. Then we simply dequeue the next item since our queue has handled the previous request already.

How do we use it?

We will create a couple of mock API endpoints that will pretend to reach out to an API and get a response. Then we will create our queue and enqueue 3 mock API calls. After that we will wrap some more calls in a setTimeout to simulate a 10 second delay and then enqueue 3 more API calls.

Mocking API Requests

For the purposes of demonstration we will create a couple of mock API methods. Imagine using a normal AJAX / AJAJ call, or any other asynchronous function. The two functions above simulate going over the wire to an API. This is a particularly slow API  that takes anywhere between 1 and 5 seconds to respond. This illustrates that it doesn’t matter how long one of the units of work takes to complete, as the next unit of work won’t start until the previous work has been completed.

Example Implementation

Now we have everything we need to test a use case for our queue. Here is an example script that simulates 3 API calls in quick succession. 10 seconds later another 3 API calls follow in quick succession. 

Example Output

Despite each function taking anywhere between 1 and 5 seconds to execute and resolve or reject, the subsequent function is not called until the previous function's resolution. If we did not enqueue these functions the output would be in a random order.

Conclusion

This example demonstrates one way to create a queue system in JavaScript. There are other ways to approach this problem, but our example illustrates how you would create a data structure to store work that you want to complete in sequence. 

We have only just scratched the surface of what can be done with Promises or how to orchestrate several asynchronous operations with interdependency at the same time. Using Promises by MDN is a helpful resource if you’re interested in discovering what else is possible!

On this blog, we share our expertise with the scientific community. You can expect to read technical content about our processes, information about our products and services, and much more. Subscribe here to receive updates!

Often when building a server-client web application, we will encounter a situation where we want to send requests to our API in the chronological order that they occur on the client. Due to the asynchronous nature of these requests, it might not be possible to send them in the same callback for the event that triggered them. This is because we want to use the response from the previous request to craft our current one. A solution to this problem would be to implement a queue. Instead of calling the API immediately after events occur, implementing a queue ensures the latest data is sent with any request.

Let’s start by breaking down some of the terminology we’ll be using.

What is a queue?

A queue is a data structure that allows you to collect individual units of work in a pile and then take from that pile at a later time. A unit of work can be anything! It might be the data you want to operate on or a function that wraps the work you want to do. In this post we’ll be pretending to fetch data from an API as an example. 

The order in which you take work from your queue is defined by your application's needs. The most commonly used method is First In First Out (FIFO), which pretty much does what it sounds like. If you enqueue 2 items, the one you added first should be worked on (dequeued) before the second item.

Items are enqueued to the back of the queue and dequeued from the front.

What is a Promise?

“A Promise is an object representing the eventual completion ( or failure ) of an asynchronous operation” - MDN

We will use Promises to call the next item from our queue. A Promise is essentially a placeholder that says, "I don't have the result of this yet, but when it completes, I will put the result here."  Promises are one of the mechanisms that allow for asynchronous code in JavaScript. You use them when you want to do some work but don’t want to be blocked by that work.

When you create a Promise you pass one function that takes two callbacks as arguments. The first callback is used when the Promise is successful or “resolved.” The second callback is used when the Promise encounters an error or is “rejected.” Promises are always in 1 of 3 possible states; pending, fulfilled, or rejected.

You can also chain Promises by using the “then” and “catch” functions. The “then” function is called when the promise is fulfilled and the “catch” function is called when the promise is rejected.

However, the “then” callback will always execute as long as the Promise is not handled in the rejected state. We will demonstrate this caveat in the example code below.

A simplified Promise lifecycle.

How do we put this together?

We start by creating a function that generates a queue to manage our API requests. It will take two arguments that we will chain to each of the Promises in our queue’s stack. The first argument will be a function that is called when the unit of work is successfully completed. The second will be called when it fails.

Next we will store the items for our queue in an array, allowing us to order the units of work. The [.inline-snippet]items[.inline-snippet] will be placed at the end / top and we will take them from the front / bottom.

Let’s skip down to the returned function aptly named [.inline-snippet]enqueue[.inline-snippet], which is the method we will use to stack items to the top / end of our queue. This function takes one argument, a function that when invoked returns a Promise, and if we only have one item in our stack we will dequeue and invoke that function right away.

Now we will look at the dequeue function. We will start by invoking the function at the bottom / front of our queue. This will return a Promise that we can chain our onResolve and onReject callbacks that we passed when we created the queue. After that we will remove the current function that we just executed from our stack since that work is now complete. Then we simply dequeue the next item since our queue has handled the previous request already.

How do we use it?

We will create a couple of mock API endpoints that will pretend to reach out to an API and get a response. Then we will create our queue and enqueue 3 mock API calls. After that we will wrap some more calls in a setTimeout to simulate a 10 second delay and then enqueue 3 more API calls.

Mocking API Requests

For the purposes of demonstration we will create a couple of mock API methods. Imagine using a normal AJAX / AJAJ call, or any other asynchronous function. The two functions above simulate going over the wire to an API. This is a particularly slow API  that takes anywhere between 1 and 5 seconds to respond. This illustrates that it doesn’t matter how long one of the units of work takes to complete, as the next unit of work won’t start until the previous work has been completed.

Example Implementation

Now we have everything we need to test a use case for our queue. Here is an example script that simulates 3 API calls in quick succession. 10 seconds later another 3 API calls follow in quick succession. 

Example Output

Despite each function taking anywhere between 1 and 5 seconds to execute and resolve or reject, the subsequent function is not called until the previous function's resolution. If we did not enqueue these functions the output would be in a random order.

Conclusion

This example demonstrates one way to create a queue system in JavaScript. There are other ways to approach this problem, but our example illustrates how you would create a data structure to store work that you want to complete in sequence. 

We have only just scratched the surface of what can be done with Promises or how to orchestrate several asynchronous operations with interdependency at the same time. Using Promises by MDN is a helpful resource if you’re interested in discovering what else is possible!

On this blog, we share our expertise with the scientific community. You can expect to read technical content about our processes, information about our products and services, and much more. Subscribe here to receive updates!

Back To Blog