154

Check out this code :

<a href="#" id="link">Link</a>
<span>Moving</span>

$('#link').click(function () {
    console.log("Enter");
    $('#link').animate({ width: 200 }, 2000, function() {
         console.log("finished");            
    });    
    console.log("Exit");    
});

As you can see in the console, the "animate" function is asynchronous, and it "fork"s the flow of the event handler block code. In fact :

$('#link').click(function () {
    console.log("Enter");
    asyncFunct();
    console.log("Exit");    
});

function asyncFunct() {
    console.log("finished");
}

follow the flow of the block code!

If I wish to create my function asyncFunct() { } with this behaviour, how can I do it with javascript/jquery? I think there is a strategy without the use of setTimeout()

Vipul Sharma
  • 525
  • 9
  • 23
markzzz
  • 47,390
  • 120
  • 299
  • 507
  • take a look at jQuery sources :) – yatskevich Mar 01 '12 at 13:19
  • The .animate() mathod uses a callback. Animate will call the callback when the animation is complete. If you need the same behaviour of .animate() what you need is a callback (called by the "main" function after some other operations). It's different if you need a "full" async function (a function called withouth blocking the execution flow). In this case you could use setTimeout() with a near 0 delay. – Fabio Buda Mar 01 '12 at 13:23
  • @Fabio Buda : why callback() should implements a sort of async? In fact, it doesnt http://jsfiddle.net/5H9XT/9/ – markzzz Mar 01 '12 at 13:45
  • in fact after "callback" I cited a "full" async method with setTimeout. I meant callback as pseudo-async in the way the function is called after other code :-) – Fabio Buda Mar 01 '12 at 15:12

12 Answers12

191

You cannot make a truly custom asynchronous function. You'll eventually have to leverage on a technology provided natively, such as:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Some HTML5 APIs such as the File API, Web Database API
  • Technologies that support onload
  • ... many others

In fact, for the animation jQuery uses setInterval.

pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • 3
    I was discussing this with a friend yesterday so this answer is perfect! I understand and can identify the async functions and use them in JS properly. But simply *why* can't we implement custom ones is not clear to me. It's like a black box that we know how make it work (using, say, `setInterval`) but that we can't even open it to see how it is done. Do you happen to have more information on the subject? – Matheus Felipe Mar 27 '15 at 22:09
  • 2
    @MatheusFelipe those functions are native to the javascript engine's implementation and the only thing you can rely on is the specs, [e.g. HTML5 timers](http://www.w3.org/TR/2011/WD-html5-20110525/timers.html) and trust the black box nature that they behave according to specs. – Spoike May 01 '15 at 05:13
  • 10
    @MatheusFelipe https://youtu.be/8aGhZQkoFbQ best talk so far regarding this topic... –  Aug 03 '15 at 15:35
  • Some implemantations, in particular Node.js, support [`setImmediate`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate) – Jon Surrell Nov 12 '15 at 13:42
  • 2
    what about `promises`. Does it give an `awaitable`? – Beingnin Feb 03 '20 at 15:45
74

You can use a timer:

setTimeout( yourFn, 0 );

(where yourFn is a reference to your function)

or, with Lodash:

_.defer( yourFn );

Defers invoking the func until the current call stack has cleared. Any additional arguments are provided to func when it's invoked.

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • 3
    This doesn't work, my javascript function that draws in a canvas keeps making the UI not responding. – gab06 Jul 26 '15 at 14:56
  • 4
    @gab06 - I'd say your canvas drawing function is blocking for its own good reasons. Split its action into many smaller ones and invoke each one of them with a timer: you'll see that the interface this way does respond to your mouse clicks, etc. – Marco Faustinelli Oct 31 '15 at 13:16
  • 1
    Minimum time for `setTimeout` is 4 milliseconds by HTML5 spec. Giving it 0 will still take that minimum amount of time. But yeah, it works well as a function deferrer. – hegez Jun 13 '18 at 17:12
  • For `scope.setTimeout` function, if the `delay` parameter is omitted, a value of `0` is used by default. – AlexMelw Aug 17 '20 at 14:50
31

here you have simple solution (other write about it) http://www.benlesh.com/2012/05/calling-javascript-function.html

And here you have above ready solution:

function async(your_function, callback) {
    setTimeout(function() {
        your_function();
        if (callback) {callback();}
    }, 0);
}

TEST 1 (may output '1 x 2 3' or '1 2 x 3' or '1 2 3 x'):

console.log(1);
async(function() {console.log('x')}, null);
console.log(2);
console.log(3);

TEST 2 (will always output 'x 1'):

async(function() {console.log('x');}, function() {console.log(1);});

This function is executed with timeout 0 - it will simulate asynchronous task

fider
  • 1,976
  • 26
  • 29
  • 7
    TEST 1 can actually only ever output '1 2 3 x' and TEST 2 is guaranteed to output '1 x' every time. The reason for the unexpected results in TEST 2 is because `console.log(1)` is called and the output (`undefined`) is passed as the 2nd arguments to `async()`. In the case of TEST 1, I think you don't fully understand JavaScript's execution queue. Because each of the calls to `console.log()` happen in the same stack, `x` is guaranteed to be logged last. I'd down-vote this answer for misinformation but don't have enough rep. – Joshua Piccari Mar 09 '15 at 03:57
  • 1
    @Joshua: It seems @fider meant to write TEST 2 as: `async(function() {console.log('x')}, function(){console.log(1)});`. – nzn Jun 04 '15 at 15:03
  • Yes @nzn and @Joshua I meant `TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});` - I corrected it already – fider Jun 08 '15 at 10:29
  • TEST 2 output is 1 x in async(function() {setTimeout(()=>{console.log('x');},1000)}, function() {console.log(1);}); – Mohsen Apr 04 '18 at 14:05
10

Here is a function that takes in another function and outputs a version that runs async.

var async = function (func) {
  return function () {
    var args = arguments;
    setTimeout(function () {
      func.apply(this, args);
    }, 0);
  };
};

It is used as a simple way to make an async function:

var anyncFunction = async(function (callback) {
    doSomething();
    callback();
});

This is different from @fider's answer because the function itself has its own structure (no callback added on, it's already in the function) and also because it creates a new function that can be used.

Ethan McTague
  • 2,236
  • 3
  • 21
  • 53
  • setTimeout can’t be used in a loop *(call the same function several times with distinct arguments)*. – user2284570 May 28 '16 at 20:21
  • @user2284570 That's what closures are for. `(function(a){ asyncFunction(a); })(a)` – Swivel Nov 22 '16 at 20:26
  • 1
    IIRC, you could also achieve this without a closure: `setTimeout(asyncFunction, 0, a);` – Swivel Nov 22 '16 at 20:28
  • 1
    If by async we mean: running in the background, parallel to the main thread then this is not truly async. All this will do is delay the execution to the process.nextTick. Whatever code you have in the function is going to be executed on the main thread. If the function was set to calculate PI then the app will freeze, with or without timeout! – Mike M Aug 04 '17 at 11:00
  • 1
    I don't understand why this answer is upvoted. When I put this in my code, the program blocks until the function is finished, which exactly what it should **not** do. – Martin Argerami Apr 20 '19 at 16:02
6

Late, but to show an easy solution using promises after their introduction in ES6, it handles asynchronous calls a lot easier:

You set the asynchronous code in a new promise:

var asyncFunct = new Promise(function(resolve, reject) {
    $('#link').animate({ width: 200 }, 2000, function() {
        console.log("finished");                
        resolve();
    });             
});

Note to set resolve() when async call finishes.
Then you add the code that you want to run after async call finishes inside .then() of the promise:

asyncFunct.then((result) => {
    console.log("Exit");    
});

Here is a snippet of it:

$('#link').click(function () {
    console.log("Enter");
    var asyncFunct = new Promise(function(resolve, reject) {
        $('#link').animate({ width: 200 }, 2000, function() {
            console.log("finished");             
            resolve();
        });    
    });
    asyncFunct.then((result) => {
        console.log("Exit");    
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#" id="link">Link</a>
<span>Moving</span>

or JSFiddle

hd84335
  • 8,815
  • 5
  • 34
  • 45
  • As I understand it, the code in the executor (the argument to `new Promise)` is run immediately, not in the next tick. So I'm not sure this answer is correct. However, it looks like the then handler is always run in a later tick. – Sam Hartman Sep 23 '20 at 14:05
6

Edit: I totally misunderstood the question. In the browser, I would use setTimeout. If it was important that it ran in another thread, I would use Web Workers.

Linus Thiel
  • 38,647
  • 9
  • 109
  • 104
6

This page walks you through the basics of creating an async javascript function.

Since ES2017, asynchronous javacript functions are much easier to write. You should also read more on Promises.

shanabus
  • 12,989
  • 6
  • 52
  • 78
3

If you want to use Parameters and regulate the maximum number of async functions you can use a simple async worker I've build:

var BackgroundWorker = function(maxTasks) {
    this.maxTasks = maxTasks || 100;
    this.runningTasks = 0;
    this.taskQueue = [];
};

/* runs an async task */
BackgroundWorker.prototype.runTask = function(task, delay, params) {
    var self = this;
    if(self.runningTasks >= self.maxTasks) {
        self.taskQueue.push({ task: task, delay: delay, params: params});
    } else {
        self.runningTasks += 1;
        var runnable = function(params) {
            try {
                task(params);
            } catch(err) {
                console.log(err);
            }
            self.taskCompleted();
        }
        // this approach uses current standards:
        setTimeout(runnable, delay, params);
    }
}

BackgroundWorker.prototype.taskCompleted = function() {
    this.runningTasks -= 1;

    // are any tasks waiting in queue?
    if(this.taskQueue.length > 0) {
        // it seems so! let's run it x)
        var taskInfo = this.taskQueue.splice(0, 1)[0];
        this.runTask(taskInfo.task, taskInfo.delay, taskInfo.params);
    }
}

You can use it like this:

var myFunction = function() {
 ...
}
var myFunctionB = function() {
 ...
}
var myParams = { name: "John" };

var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);
João Alves
  • 121
  • 1
  • 12
2
Function.prototype.applyAsync = function(params, cb){
      var function_context = this;
      setTimeout(function(){
          var val = function_context.apply(undefined, params); 
          if(cb) cb(val);
      }, 0);
}

// usage
var double = function(n){return 2*n;};
var display = function(){console.log(arguments); return undefined;};
double.applyAsync([3], display);

Although not fundamentally different than the other solutions, I think my solution does a few additional nice things:

  • it allows for parameters to the functions
  • it passes the output of the function to the callback
  • it is added to Function.prototype allowing a nicer way to call it

Also, the similarity to the built-in function Function.prototype.apply seems appropriate to me.

1

Next to the great answer by @pimvdb, and just in case you where wondering, async.js does not offer truly asynchronous functions either. Here is a (very) stripped down version of the library's main method:

function asyncify(func) { // signature: func(array)
    return function (array, callback) {
        var result;
        try {
            result = func.apply(this, array);
        } catch (e) {
            return callback(e);
        }
        /* code ommited in case func returns a promise */
        callback(null, result);
    };
}

So the function protects from errors and gracefully hands it to the callback to handle, but the code is as synchronous as any other JS function.

Mike M
  • 4,879
  • 5
  • 38
  • 58
1

Unfortunately, JavaScript doesn't provide an async functionality. It works only in a single one thread. But the most of the modern browsers provide Workers, that are second scripts which gets executed in background and can return a result. So, I reached a solution I think it's useful to asynchronously run a function, which creates a worker for each async call.

The code below contains the function async to call in background.

Function.prototype.async = function(callback) {
    let blob = new Blob([ "self.addEventListener('message', function(e) { self.postMessage({ result: (" + this + ").apply(null, e.data) }); }, false);" ], { type: "text/javascript" });
    let worker = new Worker(window.URL.createObjectURL(blob));
    worker.addEventListener("message", function(e) {
        this(e.data.result);
    }.bind(callback), false);
    return function() {
        this.postMessage(Array.from(arguments));
    }.bind(worker);
};

This is an example for usage:

(function(x) {
    for (let i = 0; i < 999999999; i++) {}
        return x * 2;
}).async(function(result) {
    alert(result);
})(10);

This executes a function which iterate a for with a huge number to take time as demonstration of asynchronicity, and then gets the double of the passed number. The async method provides a function which calls the wanted function in background, and in that which is provided as parameter of async callbacks the return in its unique parameter. So in the callback function I alert the result.

Davide Cannizzo
  • 2,826
  • 1
  • 29
  • 31
0

MDN has a good example on the use of setTimeout preserving "this".

Like the following:

function doSomething() {
    // use 'this' to handle the selected element here
}

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
xtian
  • 2,908
  • 2
  • 30
  • 43