In the example below I have a function that calls another function (which in reality may call a further function or make an Ajax request).
The example works for deferring the first function but I have no idea how to make it resolve other functions that it may call.
Do I have to pass these deferred objects around to the other functions, or is there a more elegant way around this? (In reality, I'm dealing with speech synthesis using the API callbacks so the basic structure in the example can't be changed so much).
Fiddle here
https://jsfiddle.net/macgroover/ahz46rw1/
function slowCount(numbers, deferred) {
if (deferred) {
this.deferred = jQuery.Deferred();
}
if (numbers.length) {
var number = numbers.shift();
log('SC:' + number);
setTimeout(function() {
doSomething(number);
slowCount(numbers);
}, 500);
return this.deferred.promise();
} else {
this.deferred.resolveWith(this, ['resolveWith']);
//this.deferred.resolve();
return;
}
}
function doSomething(number) {
setTimeout(function() {
log("DS:" + number);
}, 1000);
}
$(function() {
$.when(slowCount([1, 2, 3, 4], true)).done(function(rslt) {
log("All tasks finished with " + rslt);
});
});
Have a look at these rules - especially, always promisify at the lowest level, which is setTimeout for you:
function wait(timeout) {
var deferred = jQuery.Deferred();
setTimeout(deferred.resolve, timeout);
return deferred.promise();
}
Now the rest is fairly trivial:
function slowCount(numbers) {
if (numbers.length) {
var number = numbers.shift();
log('SC:' + number);
return wait(500).then(function() {
return doSomething(number);
}).then(function() {
return slowCount(numbers);
});
} else {
return $.when("end result");
}
}
function doSomething(number) {
// I assume you want this to be asynchronous as well
// - so it returns a promise!
return wait(1000).then(function() {
log("DS:" + number);
});
}
$(function() {
slowCount([1, 2, 3, 4]).then(function(rslt) {
log("All tasks finished with " + rslt);
});
});
Related
So, we currently have something like this at for asynchronous petitions to a backend.
First: a GenericApi for all the aplications
get: function (url) {
return api_bridge ({
execute: function (auth_headers) {
return $q.resolve($http.get(vm.urlBase + url, {headers: auth_headers}));
}
}).then(handlerSuccessResponse, handlerErrorResponse);
}
and then handler methods
//error
function handlerErrorResponse(response) {
return $q.reject(response);
}
//success
function handlerSuccessResponse(response) {
if (response.isAsync) {
return asyncStatus(response.taskId);
} else {
return $q.resolve(response);
}
}
Success got the key here, since if it is an async method, just recursively calls this method:
function asyncStatus(id){
return getAsyncStatus(id).then(function(response){
if (response.progress < 100){
return asyncStatus(id);
} else {
return getAsyncResult(id);
}
});
}
which calls either of those:
getAsyncStatus: function(id){
return get('/async/status/' + id);
},
getAsyncResult: function(id){
return get('/async/result/' + id);
},
(which call the first method of this list again).
And this way we can do a getter in such a way we don't ever care what happens under the hood:
get("/myurl")
.then(successMethod)
.catch(errorMethod);
(This will work same way whether it is asynchronous or not)
But now we would like to upgrade this system to be able to make callbacks everytime an asyncStatus call is made. Ideally something like this:
get("/myurl")
.then(successMethod)
.catch(errorMethod)
.progression(progressMethod);
The obvious way would be to pass a method as an argument through all the chain and then call it in every iteration of getAsyncStatus, but that kind of defeats the purpose of all this which is having a black box under the hood no one needs to worry about, plus changing this would imply changing all the current existing methods.
To make it the closest possible to the example I guess I would have to do something with $q.defer().notify() but I can't seem to grasp the concept right. Doing this doesn't work:
get("/myurl")
.then(successMethod, null, progressMethod)
.catch(errorMethod);
I have no clue how to make it work.
Well, apparently api_bridge method was breaking the promise binding. Found the solution is doing this:
Generic get:
get: function (url) {
var dfr = $q.defer();
dfr.resolve( api_bridge ({
execute: function (auth_headers) {
return $q.resolve($http.get(vm.urlBase + url, {headers: auth_headers}));
}
}));
return dfr.promise.then(handlerSuccessResponse, handlerErrorResponse);
}
Handler methods (no change):
//error
function handlerErrorResponse(response) {
return $q.reject(response);
}
//success
function handlerSuccessResponse(response) {
if (response.isAsync) {
return asyncStatus(response.taskId);
} else {
return $q.resolve(response);
}
}
Recursive async method:
function asyncStatus(id){
var deferred = $q.defer();
var deferred.promise.then(null, null, angular.noop);
var deferred.notify("This is a notification");
deferred.resolve(getAsyncStatus(id).then(function(response){
if (response.progress < 100){
return asyncStatus(id);
} else {
return getAsyncResult(id);
}
}));
return deferred.promise;
}
which calls either of those (no change);
getAsyncStatus: function(id){
return get('/async/status/' + id);
},
getAsyncResult: function(id){
return get('/async/result/' + id);
},
And now we can do:
get("/myurl")
.then(successMethod, null, progressMethod)
.catch(errorMethod);
Callback progressMethod will be called everytime asyncStatus throws a notification.
I have a JavaScript class that is meant to help deal with promises. First you add functions to an array, then it executes them pops them and calls itself to do the next one. At the end of the array it resolves that promise. My hope was to then propagate the resolution all the way up the stack of recursive calls. This will allow you to force multiple asynchronous functions to run sequentially using a simple set of commands. furthermore employ logic to modify the flow of the ansync functions.
function Sequencer() {
this.functionSequence = [];
this.addFunction = function (func) {
this.functionSequence.push(func);
}
this.getFunctionSequence = function () {
return functionSequence;
}
this.executeAll = function () {
var functionList = this.functionSequence;
var deferred = $q.defer();
if (functionList.length > 0) {
functionList[0]().then(function (result) {
if (result) {
functionList.splice(0, 1);
executeAll().then(function (resultInner) {
if (resultInner == true) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
});
} else {
functionList = [];
deferred.resolve(false);
}
});
} else {
deferred.resolve(true);
}
return deferred.promise;
}
}
I am getting ReferenceError: 'executeAll' is undefined
in this script, on the recursive call line "executeAll' just after the splice
the first function in the array is being executed(I was testing it with a modal pop up) and when it resolves it hits the splice, then it throws the error right on the executeAll line. Am I defining the function incorrectly? Am I calling it correctly as a recursive function?
use this.executeAll - assuming this will be correct, which it wont, so you'll need to account for that as well ... something like var self = this at the top of executeAll, then call self.executeAll
this.executeAll = function() {
var functionList = this.functionSequence;
var deferred = $q.defer();
var self = this; // save reference to this
if (functionList.length > 0) {
functionList[0]().then(function(result) {
if (result) {
functionList.splice(0, 1);
// need to use self here because "this" is not the "this" we want
self.executeAll().then(function(resultInner) {
if (resultInner == true) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
});
} else {
functionList = [];
deferred.resolve(false);
}
});
} else {
deferred.resolve(true);
}
return deferred.promise;
};
The reason this is not the this you "want" is due to how this works in javascript - there is plenty on info on stack exchange about using this - I'll find and link a good answer shortly
I offer this alternative code
this.executeAll = function() {
return this.functionSequence.reduce(function(promise, item) {
return promise.then(function(result) {
if (result) {
return item();
}
else {
throw "Fail"; // throw so we stop the chain
}
});
}, Promise.resolve(true))
.then(function(result) {
this.functionSequence = []; // clear out the added functions
return true; // fulfilled value is true as per original code
}.bind(this), function(err) {
this.functionSequence = []; // clear out the added functions
if (err == "Fail") {
return false; // convert the "Fail" to a fullfilled value of false as per original code
}
else {
throw err; // any other error - re-throw the error
}
}.bind(this))
};
let's take this as an example:
I have 3 urls in an array urls
require function returns a promise which just makes an $http call
this is a working code, but as the array can be '1 to n' this is obviously not what I want.
I need the 3 require as a waterfall, not in parallel.
in the last promise, I need to resolve a final promise which is the var deferred.
require(urls[0]).then(function () {
require(urls[1]).then(function () {
require(urls[2]).then(function () {
deferred.resolve();
});
});
})
this approach is not working, because this will do all the $http calls in parallel.
var promises = [];
angular.forEach(urls, function (value) {
promises.push(require(value));
});
$q.all(promises).then(function () {
deferred.resolve();
});
is there a nice way to do this with a for/cycle?
Create a function to handle the iterations:
function go (urls) {
if (urls[0]) {
require(urls[0]).then(function () {
go(urls.slice(1));
});
}
}
go(urls);
Here is an excellent blog post: http://www.codeducky.org/q-serial/
I will share only the part that is relevant.
First we define this helper method:
function serial(tasks) {
var prevPromise;
angular.forEach(tasks, function (task) {
//First task
if (!prevPromise) {
prevPromise = task();
} else {
prevPromise = prevPromise.then(task);
}
});
return prevPromise;
}
Then we use it.
serial([
function() { return require(urls[0]) },
function() { return require(urls[1]) },
function() { return require(urls[2]) }
]).then(function () {
deferred.resolve();
});
Just to offer another way, there's a "one-line" solution to this without having to make another method:
return promises.reduce($q.when, promises[0]);
See demo here: http://plnkr.co/edit/xlGxYj57lzXdAMM5Iv6s?p=preview (I changed the default $q.when to something else to show handling each promise.)
Update: Made the plunker more representative of OP's scenario.
I have a simple "async" JS function:
function asyncFunc(i) {
setTimeout(function () {
console.log(i);
}, 1000);
}
if I want to execute this asyncFunc 5 times in a for loop, i.e. log 1 - 5 per second, and totally costs 5 seconds.
1
2
3
4
5
I know jQuery's when().done() can do that however if I am in a environment with NO 3rd party JS libraries, what is the simplest and elegant way to achieve this?
Actually for example I want to write a util function which accepts an array of async functions, and this util function can execute passed in functions one by one:
function execAsyncTasks([asyncTask1, asyncTask2, asyncTask3]) {
asyncTask1();
// Wait until asyncTask1 finished
asyncTask2();
// Wait until asyncTask2 finished
asyncTask3();
// Wait until asyncTask3 finished
}
All your tasks will have to implement some sort of callback mechanism, because that's the only way you'll ever know that an asynchronous task has been completed. Having that, you could do something like:
function executeTasks() {
var tasks = Array.prototype.concat.apply([], arguments);
var task = tasks.shift();
task(function() {
if(tasks.length > 0)
executeTasks.apply(this, tasks);
});
}
executeTasks(t1, t2, t3, t4);
Demo
You can use Async module:
https://github.com/caolan/async
async.parallel([
function(){ ... },
function(){ ... }
], callback);
async.series([
function(){ ... },
function(){ ... }
]);
This is one approach of many
function execAsyncTasks(asyncTask1, asyncTask2, asyncTask3) {
var count = 0;
function nextCommand() {
switch (count) {
case 0:
asyncTask1();
break;
case 1:
asyncTask2();
break;
case 2:
asyncTask3();
default:
return;
}
count++;
setTimeout(nextCommand, 1000);
}
nextCommand();
}
you can have a sync mechanism using callbacks and recursive function calls: see http://jsfiddle.net
function asyncFunc(i, callback) {
setTimeout(function() {
document.body.innerHTML += '<p>' + i + '</p>';
callback();
}, 1000);
}
var args = [0, 1, 2, 3, 4];
function loopThroughArgs(callback) {
if (args.length == 0) {
callback();
} else {
asyncFunc(args[0], function() {
args.splice(0, 1); //remove the first item from args array
loopThroughArgs(callback);
});
}
}
loopThroughArgs(function() {
document.body.innerHTML += '<p>done !</p>';
});
Check out this code :
Link
<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()
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.
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.
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
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.
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.
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>
Link
<span>Moving</span>
or JSFiddle
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.
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);
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.
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.
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.
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);
});