Finally async
/await
will be supported in all major browser soon except IE.
So now we can start writing more readable code with async
/<
I actually just did this same thing. By using promises and then Promise.all to synchronize them at the end, you can do many concurrent requests, but then be sure you have all the results back before you finish.
See here in the last example: http://javascriptrambling.blogspot.com/2017/04/to-promised-land-with-asyncawait-and.html
You can write something like this:
const responses = await Promise.all([
fetchUserAsync(),
fetchPostsAsync(),
]);
const userResponse = responses[0];
const postsResponse = responses[1];
This is easy right? But there is a catch. Promise.all
has fail-fast behaviour which means, it will reject as soon as one of the promises rejected. Probably you want a more robust solution where we are in charge of handling the rejections any of the fetches. Luckily there is a solution, it can be achieved simply with async
/await
without the need of using Promise.all
. A working example:
console.clear();
function wait(ms, data) {
return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}
/**
* This will run in series, because
* we call a function and immediately wait for it's result,
* so this will finish in 1s.
*/
async function series() {
return {
result1: await wait(500, 'seriesTask1'),
result2: await wait(500, 'seriesTask2'),
}
}
/**
* While here we call the functions first,
* then wait for the result later, so
* this will finish in 500ms.
*/
async function parallel() {
const task1 = wait(500, 'parallelTask1');
const task2 = wait(500, 'parallelTask2');
return {
result1: await task1,
result2: await task2,
}
}
async function taskRunner(fn, label) {
const startTime = performance.now();
console.log(`Task ${label} starting...`);
let result = await fn();
console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}
void taskRunner(series, 'series');
void taskRunner(parallel, 'parallel');
/*
* The result will be:
* Task series starting...
* Task parallel starting...
* Task parallel finished in 500 milliseconds with, { "result1": "parallelTask1", "result2": "parallelTask2" }
* Task series finished in 1001 milliseconds with, { "result1": "seriesTask1", "result2": "seriesTask2" }
*/
Note: You will need a browser which has async
/await
enabled to run this snippet (or nodejs v7 and above)
This way you can use simply try
/ catch
to handle your errors, and return partial results inside the parallel
function.
For those asking how you extend this to a run-time determined number of calls, you can use 2 loops. The first starts all the tasks, the second waits for everything to finish
console.clear();
function wait(ms, data) {
return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}
/**
* While here we call the functions first,
* then wait for the result later, so
* this will finish in 500ms.
*/
async function runTasks(timings) {
let tasks = [];
for (let i in timings) {
tasks.push(wait(timings[i], `Result of task ${i}`));
}
/* Want fast fail? use Promise.All */
//return Promise.All(tasks);
let results = [];
for (let task of tasks) {
results.push(await task);
}
return results;
}
async function taskRunner(fn, arg, label) {
const startTime = performance.now();
console.log(`Task ${label} starting...`);
let result = await fn(arg);
console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}
void taskRunner(runTasks, [50,100,200,60,500], 'Task List');
The pseudo code can be written as below:
fn async task() {
result-1 = doAsync();
result-2 = doAsync();
result-n = doLongAsync();
try{
// handle results together
combinedResult = handleResults(await result-1, await result-2);
lastResult = handleLastResult(await result-n);
}
catch(err){
console.error(err)
}
}
result-1, result-2, result-n will run in parallel. combinedResult and lastResult will also run in parallel. However combinedResult value i.e. return of handleResults function will be returned once the result-1 and result-2 are available and lastResult value i.e handleLastResult will be returned once the result-n is available.
Hope this helps
First, are your code a blocking-code?
If yes, remember that javascript is single thread, so you cannot run two synchronous codes, for example two loops (for or while) at the same time.
But, it is possible to achieve that using Web Workers, I managed to execute functions in generic web workers and without using separated js files.
setInterval(()=>{console.log("non blocked " + Math.random())}, 900)
console.log("start blocking code in parallel in web Worker")
console.time("blocked")
genericWorker(window, ["blockCpu", function (block){
block(10000) //This blockCpu function is defined below
return "\n\nbla bla\n" //This is catched in the resolved promise
}]).then(function (result){
console.timeEnd("blocked")
console.log("End of blocking code", result)
})
.catch(function(error) { console.log(error) })
/* A Web Worker that does not use a File, it create that from a Blob
@cb_context, The context where the callback functions arguments are, ex: window
@cb, ["fn_name1", "fn_name2", function (fn1, fn2) {}]
The callback will be executed, and you can pass other functions to that cb
*/
function genericWorker(cb_context, cb) {
return new Promise(function (resolve, reject) {
if (!cb || !Array.isArray(cb))
return reject("Invalid data")
var callback = cb.pop()
var functions = cb
if (typeof callback != "function" || functions.some((fn)=>{return typeof cb_context[fn] != "function"}))
return reject(`The callback or some of the parameters: (${functions.toString()}) are not functions`)
if (functions.length>0 && !cb_context)
return reject("context is undefined")
callback = fn_string(callback) //Callback to be executed
functions = functions.map((fn_name)=> { return fn_string( cb_context[fn_name] ) })
var worker_file = window.URL.createObjectURL( new Blob(["self.addEventListener('message', function(e) { var bb = {}; var args = []; for (fn of e.data.functions) { bb[fn.name] = new Function(fn.args, fn.body); args.push(fn.name)}; var callback = new Function( e.data.callback.args, e.data.callback.body); args = args.map(function(fn_name) { return bb[fn_name] }); var result = callback.apply(null, args) ;self.postMessage( result );}, false)"]) )
var worker = new Worker(worker_file)
worker.postMessage({ callback: callback, functions: functions })
worker.addEventListener('error', function(error){ return reject(error.message) })
worker.addEventListener('message', function(e) {
resolve(e.data), worker.terminate()
}, false)
//From function to string, with its name, arguments and its body
function fn_string (fn) {
var name = fn.name, fn = fn.toString()
return { name: name,
args: fn.substring(fn.indexOf("(") + 1, fn.indexOf(")")),
body: fn.substring(fn.indexOf("{") + 1, fn.lastIndexOf("}"))
}
}
})
}
//random blocking function
function blockCpu(ms) {
var now = new Date().getTime(), result = 0
while(true) {
result += Math.random() * Math.random();
if (new Date().getTime() > now +ms)
return;
}
}
If you're ok with the fail-fast behavior of Promise.all and the destructuring assignment syntax:
const [userResponse, postsResponse] = await Promise.all([
fetchUserAsync(),
fetchPostsAsync(),
]);