fafafariba
fafafariba

Reputation: 345

Javascript Coding Challenge with setTimeout/Asynchronous Output

I am trying solve the following challenge where I have to write a function triggerActions that passes a callback into the processAction, and produces the output:

"Process Action 1"
"Process Action 2"
...
"Process Action n"

Here is the provided function:

function processAction(i, callback) {
  setTimeout(function() {
    callback("Processed Action " + i);
  }, Math.random()*1000);
}

Function to code:

function triggerActions(count) {

}

Note that the code for processAction cannot be altered. I was thinking of using a Promise but I'm not sure how. I believe the setTimeout is actually synchronous so I don't know if async/await would work.

My attempt:

triggerActions = count => {
    let promises = [];
    for(let i=1; i<=count; i++) {
    promises.push(new Promise( (resolve, reject) => processAction(i, str => resolve(str))));
    }
    let results = []
    promises.forEach( promise => Promise.resolve(promise).then( async res => results.push(await res)));
    return results;
}

Upvotes: 2

Views: 1071

Answers (7)

Alessandro Scandone
Alessandro Scandone

Reputation: 157

Here's an overview of all the possible approaches:

Callback-based:

Sequential:

function triggerActions(count) {
  ;(function recur(i = 0) {
    processAction(i, (data) => {
      console.log(data)
      if (i < count) {
        recur(i + 1)
      }
    })
  })()
}

Concurrent

function triggerActions(count) {
  const data = Array.from({ length: count })
  for (let i = 0; i < count; i++) {
    processAction(i, (result) => {
      data[i] = result
      count--
      if (count == 0) {
        for (const x of data) {
          console.log(x)
        }
      }
    })
  }
}

Promise-based:

We can use this function to make processAction async:

function processActionP(i) {
  return new Promise((res) => processAction(i, res))
}

Sequential:

async function triggerActions(count) {
  for (let i = 0; i < count; i++) {
    const data = await processActionP(i)
    console.log(data)
  }
}

Concurrent:

async function triggerActions(count) {
  const data = await Promise.all(
    Array.from({ length: count }, (_, i) => processActionP(i)),
  )
  for (const x of data) {
    console.log(x)
  }
}

Concurrent, using lodash/fp

const _ = require('lodash/fp')

const triggerActions = _.pipe(
  _.range(0),
  _.map(processActionP),
  Promise.all.bind(Promise),
  data => data.then(
    _.each(console.log)
  ),
)

Upvotes: 0

Faraz Haider
Faraz Haider

Reputation: 51

The requirements are that the function ‘processAction’ should remain unchanged and invoked in a batch.

For this I have used the util.promisify function that takes a function and converts it into a promise. A promise can be invoked in a batch with Promise.all.

Another requirement is that the callback should output “Processed Action i” where i is a number. The anonymous function ‘func’ has been defined to do this.

The triggerActions function takes a number, x, creates an array of numbers containing indices from 0 to x and then invokes a count of x asynchronous functions simultaneously.

const {promisify} = require('util');

function processAction(i, callback) {
    setTimeout(function() {
      callback("Processed Action " + i);
    }, Math.random()*1000);
}
const func = (param1) => console.log(param1);
const promisifyedProcessAction = promisify(processAction);


async function triggerActions(count) {
    const arr = [];
    for(let i = 0; i < count;)
        arr.push(++i);    
    await Promise.all(
        arr.map((value) => promisifyedProcessAction(value,func)));
}

triggerActions(5);

Upvotes: 0

Gabor Szekely
Gabor Szekely

Reputation: 1238

Here is my solution using Promise.all:

function triggerActions(count) {
  const promises = range(count).map(
    i => new Promise(resolve => processAction(i, resolve))
  );

  Promise.all(promises).then(results => {
    results.forEach(result => console.log(result));
  });
}

// Generates an array from 1...n
function range(n) {
  return Array.from({ length: n }, (_, i) => i + 1);
}

Upvotes: 0

Simon Situ
Simon Situ

Reputation: 1

Here's my solution:

function processAction(i, callback) {
  setTimeout(function() {
   callback("Processed Action " + i);
  }, Math.random()*1000);
}
// Function to code:

function triggerActions(count) {
  const asyncArr = [];
  for (let i = 1; i <= count; i++) {
    asyncArr.push(new Promise(resolve => processAction(i, resolve)));
  }
  Promise.all(asyncArr).then((vals) => {
    vals.forEach((val) => console.log(val))
  });
}

triggerActions(5);

Upvotes: 0

Jacob Worrel
Jacob Worrel

Reputation: 11

The original poster's instinct to use promises was correct.

The two solutions above may work but because each call to triggerActions() has to wait for the delay to elapse before the next call can be made, this is considerably slow.

Maybe this is what you want but here's an optimized solution using promises and Promise.all():

const processAction = (i, callback) => {
  setTimeout(function() {
    callback("Processed Action " + i);
  }, Math.random()*1000);
}

const triggerActions = (n) => {
  const promises = [];
  const generatePromise = (i) => {
    return new Promise((resolve, reject) => {
      processAction(i, resolve);
    });
  }
  for (let i = 1; i <= n; i += 1) {
    promises.push(generatePromise(i));
  }
  Promise.all(promises)
    .then((strings) => strings.forEach((string) => console.log(string)));
}

triggerActions(10);

To compare the performance differences, try running the two approaches side by side.

Upvotes: 1

Gyum Fox
Gyum Fox

Reputation: 3647

There you go:

// Your unaltered function
function processAction(i, callback) {
  setTimeout(function() {
    callback("Processed Action " + i);
  }, Math.random()*1000);
}

// The function you want to implement
function triggerActions(count) {  
  var triggerAction = function (i) {    // Local function to process the given action number:
    if (i <= count) {                   // More actions to execute?
      processAction(i, function (text) {// Process current action number and pass a callback in parameter
        console.log(text);              // Write the result of processAction             
        triggerAction(i + 1);           // Trigger the next action 
      });                               //
    }                                   //
  }                                     
  triggerAction(1);                     // First things first: start at action one
}

// Call the function
triggerActions(10);

Upvotes: 1

Mark
Mark

Reputation: 92461

I kind of like short and sweet:

var n = 5
var stop = 1

triggerActions = function(text) {
    if (text) console.log(text)
    if (stop <= n){
        processAction(stop++, triggerActions)
    }
}
triggerActions()

P.S

It occurred to me that perhaps you are only allowed to provide a function which means the stop variable declaration outside the function is a problem. It makes it a little more verbose, but you can wrap it all inside the function like this:

function triggerActions(stop) {
    var rFn = (text) => {
        if (text) console.log(text)
        if (stop <= n){
            processAction(stop++, rFn)
        }
    }
    rFn()
}
triggerActions(1)

Upvotes: 1

Related Questions