jQuery 3 - Promises, Deferred & WebWorkers
Written by Ian Elliot   
Thursday, 07 September 2017
Article Index
jQuery 3 - Promises, Deferred & WebWorkers
Threads & Communication

It is fairly easy to consume promises returned by asynchronous functions that other programmers have put together for you. It is only a little more difficult to use promises to create your own asynchronous functions that run in parallel a non-UI thread. 

Just jQuery
Events, Async & AJAX

Is now available as a print book: Amazon 

jquery2cover

 

Contents

  1. Events, Async & Ajax (Book Only)
  2. Reinventing Events
  3. Working With Events
  4. Asynchronous Code
  5. Consuming Promises
  6. Using Promises 
  7. WebWorkers
  8. Ajax the Basics - get
  9. Ajax the Basics -  post
  10. Ajax - Advanced Ajax To The Server
  11. Ajax - Advanced Ajax To The Client
  12. Ajax - Advanced Ajax Transports And JSONP
  13. Ajax - Advanced Ajax The jsXHR Object
  14. Ajax - Advanced Ajax Character Coding And Encoding 

Also Available:

buy from Amazon

smallcoverjQuery

 

Advanced Attributes

 

It is assumed that you already know about jQuery's Deferred and promise objects. If not read the previous two chapters. 

Custom Async With A Single Thread

So far we have used promises to make existing asynchronous features like Ajax easier to work with. We have also looked at how to create our own custom asynchronous functions. However these don't really have the same characteristics as the built-in asynchronous functions. When you start an Ajax file transfer for example the UI thread is able to get on with other things while the system handles the file transfer and signals back when it is complete. That is in one way or another true asynchronous operations do involve other threads if only behind the scenes. 

However it is still possible to convert a potentially long running function into one that is executed on the UI thread in short bursts. For an example see the computePiAsync function as described in chapter 3. It is also possible and useful to use promises to make the function easier to use. All it has to do is return a promise and call resolve when it has finished. It can also optionally use the notify function to return an intermediate result. 

For example the computePiAsync function is easy to convert to use a promise and support notification:

function computePiAsync(end)
{
 var state = {};
 state.k = 0;
 state.pi = 0;
 var d = $.Deferred();
 
 function computePi() {
  if (state.k >= end) d.resolve(state);
  var i;
  for (i = 0; i < 1000; i++) {
    state.k++;
    state.pi += 4 * Math.pow(-1, state.k + 1) /
                              (2 * state.k - 1);
  }
  d.notify(state);
  setTimeout(computePi, 0);
 }
 setTimeout(computePi, 0);
 return d.promise();
}

The first and main difference is that now we create a Deferred and return its associated promise . Notice that the computation is run until the total number of loops is equal to or greater than the end value specified and this results in the resolve function being called. The value passed back to the promise is that state object. It is a good idea to try and only pass back a single value to stay compatible with JavaScript promises.  After each block of 1000 computations the notify method is called to pass the intermediate result back to the main program.

The main program would use the new promise returning version of the function something like:

var p = computePiAsync(100000);
p.progress(function (value) {
             $("#result").text(value.pi);
             $("#count").text(value.k);
           });
p.then(function (value) {
             $("#result").text(value.pi);
             $("#count").text("Final value " + value.k);
           });

You can see that the progress is displayed and a final value. 

The same approach works for other long running operations.

  • Keep the state in a state object and terminate the function after doing a small amount of work.
  • Arrange to restart the function for the next block of work by using setTimeout with a delay of zero.
  • Return a promise object and call notify at the end of each block and call resolve at the end for the entire calculation. If there is an error also remember to call reject.   

Web Worker Based Promises

The sort of asynchronous function we have just constructed is more like simulated async rather than the real thing. It is more like setting up a timeout event so that you can do some work in small chunks and not block the UI thread. 

A second type of asynchronous programming that occurs in practice is where you use another thread of execution to complete a long running task while the UI thread gets on with what it is supposed to do i.e. look after the UI. For example, when you start an Ajax file download the operating system allocates another thread to look after the task. From the point of view of a JavaScript programmer it all seems magical. You ask for a file and some time later the file is available and all without the UI thread having to do anything at all. This is usually how we view asynchronous code in JavaScript but behind the scenes the OS is busy scheduling threads. You can think of the previous example of using setTimeout to schedule the running of a function as doing the scheduling job that OS takes on when you use another thread. However there is also the very real possibility that the hardware supports multiple cores and the other thread might well be really running in parallel with the UI thread. That is the OS can implement true parallel execution and this makes thing work faster. 

Until recently there was no way that a JavaScript programmer could take advantage of the OS to schedule threads or of multiple cores to implement true parallelism but now we have the web worker which implements background processing on a non-UI thread. 

There is no doubt that the best way to package a worker thread is as a promise. This isn't difficult but there are are some subtle points and it is easy to become confused.

To wrap a long running function that uses a worker thread as a promise, all we have to do is arrange create a function that returns a promise object. The interesting thing is that the worker code doesn't generally need any changes to make use of a promise. It simply computes the answer or completes the task and then uses the postMessage method to trigger a message event on the UI thread and to return the result.

First we need to recap the basic principles of using a web worker.

Basic Web Worker

The good news is that Web Worker is very easy to use.

What is slightly difficult to get to grips with  is working out what you are not allowed to do and achieving simple communication between the threads.

Ideally you should wrap any Web Worker tasks you create as promises to make the code easier to use. We will take a look at how to do this using jQuery later - for the moment let's concentrate on using Web Workers raw.

There really is only one key object when using Web Workers, the Worker object. 

The worker object automatically starts a new thread and begins executing JavaScript code as soon as it is created.  

The basic action is that it loads the JavaScript file that you specify in its constructor and starts the script executing on a new thread. It is possible to avoid using a separate file to store the code but it is messy and best avoided. The reason the code is in a separate file is to keep the execution contexts separated on the threads.That is the program that starts on the new worker thread has no shared variables with the code that creates it. 

So for example, if you have a program stored in myScript.js the instruction:

var worker=new Worker("myScript.js");

Although this is simple there is a subtly that you need to get clear if you are to avoid making silly mistakes. 

When you create a Worker object two things happen.

  1. the code stored in myScript.js or whatever file you specify is loaded and set running using a new OS level thread. 

  2. A Worker object is created on the UI thread and this is an object that your "standard" JavaScript code running on the UI thread can work with.

If you think that this is obvious and doesn't need to be said, so much the better.

Banner



Last Updated ( Thursday, 05 May 2022 )