2

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;
}
fafafariba
  • 335
  • 1
  • 2
  • 12
  • 2
    setTimeout is actually **a**synchronous - have you tried **any** code, ro do you want someone to solve the challenge in total for you? – Jaromanda X Jul 17 '17 at 04:58
  • [promisify](https://stackoverflow.com/q/22519784/1048572) `processAction`, then `async`/´await` will work. – Bergi Jul 17 '17 at 05:11
  • @JaromandaX I just edited my post with my code. – fafafariba Jul 17 '17 at 05:23
  • You cannot [use `forEach` with an `async function`](https://stackoverflow.com/q/37576685/1048572)!+ – Bergi Jul 17 '17 at 05:35
  • 1
    Do you want it printed in sequential order, (i.e 1, 2, 3, 4.. n) or the order they are executed in (i.e. random order)? – H77 Jul 17 '17 at 05:53

7 Answers7

1

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)
Mark
  • 90,562
  • 7
  • 108
  • 148
1

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);
Gyum Fox
  • 3,287
  • 2
  • 41
  • 71
  • I'd recommend to put the condition right in front of the `i++` instead of in the callback, so that your code doesn't fail when `count` is zero. – Bergi Jul 17 '17 at 07:23
  • @Bergi thanks you are right. I updated the code and I also made it cleared by working with 1-based index . – Gyum Fox Jul 17 '17 at 07:30
1

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.

Jacob Worrel
  • 11
  • 1
  • 3
0

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);
0

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);
}
Gabor Szekely
  • 1,108
  • 5
  • 14
0

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);
0

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)
  ),
)