简体   繁体   中英

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;
}

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

PS

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)

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

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.

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

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM