4

I have config like JSON where we can define any JavaScript functions inside. Now I have execution function which would take that array of functions and execute. How can I do that?

const listOfFuncs = [
  {
    "func1": (...args) => console.log(...args)
  },
  {
    "func2": async (...args) => {
      return await fetch('some_url');
    }
  }
]

function execute() {
  // how to execute the above array of functions now ?
}

// Should this be called as await execute()? 
execute();

As you can see one function sync & another function as async & await. Defining everything function as async & await seems bad ( creating a lot of new promises ) + I can't define all function as synchronous also.

Thanks for your answer in advance.

Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62
Sathish
  • 2,056
  • 3
  • 26
  • 40
  • You don't need to define functions as `async` to `await` their results. Any value can be `await`ed, not just promises. – Bergi Dec 18 '20 at 11:24
  • Do you want the async parts to happen concurrently? – Bergi Dec 18 '20 at 11:25
  • @Bergi No, serial execution is fine for now. But it would be wonderful to know if I can achieve concurrency also. – Sathish Dec 18 '20 at 11:26

6 Answers6

7

You can use Promise.all() to resolve an array of promises.

Values other than promises will be returned as-is

const listOfFuncs = [
  () => 45,
  async () => new Promise(resolve => {
      setTimeout(() => resolve(54), 100);
  }),
  () => Promise.resolve(34)
];

// Convert to list of promises and values
const listOfMixedPromisesAndValues = listOfFuncs.map(func => func());

// Promise.all returns a Promise which resolves to an array
// containing all results in the same order.
Promise.all(listOfMixedPromisesAndValues).then(result => {
  // Logs: [45, 54, 34] after 100ms
  console.log(result)
});

There is no native function to resolve an object containing promises.

However some libraries implement alternative Promise API, to make it easier to use more complex pattern (cancellation, races, ...). The most well known is Bluebird.

It implements a Promise.props methods which almost do what you want: http://bluebirdjs.com/docs/api/promise.props.html

var Promise = require("bluebird");

Promise.props({
    pictures: getPictures(),
    comments: getComments(),
    tweets: getTweets()
}).then(function(result) {
    console.log(result.tweets, result.pictures, result.comments);
});
Eloims
  • 5,106
  • 4
  • 25
  • 41
  • 1
    Not sure why you mention objects containing promises, the OP doesn't have these anywhere? – Bergi Dec 18 '20 at 11:39
  • 1
    @Bergi in the OP question, it kinda looks like (s)he wants to retrieve the results in an object with the provided keys ('func1', 'func2', ...). I'm not sure why (s)he nesting those objects in the array values, I can only guess! – Eloims Dec 18 '20 at 11:42
  • Oh, wow, I skipped over that completely. The name `listOfFuncs` implied an array of functions to me… – Bergi Dec 18 '20 at 11:44
  • Doing `listOfFuncs.flatMap(o => Object.values(o).filter(v => typeof v === "function"))` will return a clean, flat Array – blex Dec 18 '20 at 11:50
4

You should simply treat all your functions as potentially returning a promise. No need to distinguish them, just await the result. The execution will carry on immediately if it was not a thenable (read: non-promise) value. So just write

async function execute() {
  for (const func of listOfFuncs) {
    await func();
  }
}

If you want the asynchronous tasks to run concurrently, just collect all values into an array and pass them to Promise.all. It deals with non-promise elements just fine as well.

async function execute() {
  await Promise.all(listOfFuncs.map(func => func()));
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

Solution without promises. Use process.nextTick(callback) or setImmediate

const listOfFuncs = [
    {
        "func3": setImmediate(execute, "setImmadiate executes after process.nextTick")
    }, 
    {
        "func2": process.nextTick(execute, "process.nextTick executes after sync function but before setImmediate")
    },
    {
        "func1": execute
    }
]

function execute() {
    console.log("executing...", arguments[0]);

}

execute(listOfFuncs);

// results: 
// executing...[...]
// executing...process.tick
// executing...setImmediate...
rags2riches-prog
  • 1,663
  • 1
  • 10
  • 22
0

In this example, you create an array of the executed functions and use the Promise.all() with a map to get all promisses if the function results. (in a case of an async function the function returns a promise-value, which you can await)

function isPromise(promise) {  
    return !!promise && typeof promise.then === 'function'
}
let functions = [
  (() => {})(),
  (async () => {})()
];
await Promise.all(functions.map(function_result => (isPromise(function_result) ? function_result : undefined)) 
Paul Werner
  • 87
  • 1
  • 8
-1

Maybe this is a solution for you:

await Promise.all([
  (async () => {
    //Your Function here
  })(),
]);
Paul Werner
  • 87
  • 1
  • 8
  • its an array of async function, which are not wrapped into an object. You can also just put the async function you like into the array without using (async() => {})() – Paul Werner Dec 18 '20 at 11:25
  • 1
    Hey Paul, thanks for answering but that requires all the functions to be promise. – Sathish Dec 18 '20 at 11:25
  • simplest solution: wrap all function into a async function – Paul Werner Dec 18 '20 at 11:27
  • Hey Paul, but that would create a lot of un-needed promises. I heard wrapping normal function with promise has perf impact. – Sathish Dec 18 '20 at 11:28
  • 2
    @Sathish `wrapping normal function with promise has perf impact` Generally speaking, these days that's really not much of an issue now that Browsers have native support for Promises & async / await. etc. To give you an example, recently I created a `async readByte` function in Node.js, that would chunk the data in, (reason for the promise). And it was no slower than reading the whole binary file in, and traversing the buffer. I was pretty surprised too, as I was expected some slow down. – Keith Dec 18 '20 at 11:39
-1

Ideally having everything async would be cleaner. But if not, this would work :

async function execute(...args) {
  for (let i = 0; i < listOfFuncs.length; i++) {
    if (listOfFuncs[i].constructor.name === "AsyncFunction") {
      await listOfFuncs[i](...args);
    } else {
      listOfFuncs[i](...args);
    }
  }
}
Easwar
  • 5,132
  • 1
  • 11
  • 21
  • 2
    You can remove that `if/else` and just `await` all of them. It does not need to be async to be `await`ed – blex Dec 18 '20 at 11:31
  • 1
    Also, that `if` would not work if the function returns a Promise without using `async/await`. The constructor would just be `Function`, so undistinguishable from a sync function before calling it – blex Dec 18 '20 at 11:36