Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Array.prototype.some() with an async function?

I am trying to do the following:

command.permissions.some(async permissionsKey => {
        switch (permissionsKey) {
            case "all": {
                return true;
            }
            case "OWNER": {
                return await msg.roomContext.isRoomOwnerId(msg.getStaticUserUID());
            }
            default: {
                return config.users_groups[permissionsKey].includes(msg.getStaticUserUID());
            }
        }
    });

However it always is true because Array.prototype.some does not expect an async function so a promise is returned when the function is called. A Promise is truthy.

I was wondering the best away to use an async function with any of the Array.prototype functions, specifically the some function.

like image 485
JBis Avatar asked Jun 12 '19 00:06

JBis


People also ask

How do I return an array from async function?

We have to call the async function from another function which can be asynchronous or synchronous (We can pass the array or choose to declare the array in the async function itself) and then return the array from the async function. The basic approach is to include a try-catch block.

How do you handle promises in asynchronous function?

Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.

Can I use async in filter?

For instance, using a Promise returned from an asynchronous call as an array filter condition is not possible because the Promise object itself evaluates to true. Previous iterations of asynchronous code in JavaScript utilised callbacks (AsyncJS).

Can async functions return values?

Async methods can have the following return types: Task, for an async method that performs an operation but returns no value. Task<TResult>, for an async method that returns a value.


3 Answers

If you want the result as soon as the first promise resolves with true, you can do that too:

const somePromise = promises =>
    new Promise((resolve, reject) => {
        let resolveCount = 0;
        const resolved = value => {
            if (value) {
                resolve(true);
            } else if (++resolveCount === promises.length) {
                resolve(false);
            }
        };

        for (const promise of promises) {
            promise.then(resolved, reject);
        }
    });

Alternative fancy approach:

const never = new Promise(() => {});

const somePromise = promises => Promise.race([
    Promise.race(promises.map(async p => !!await p || never)),
    Promise.all(promises).then(r => r.some(Boolean)),
]);

In your specific case, though, since there’s at most one promise, there’s a much better way to do it:

let hasPermission =
    command.permissions.some(permissionsKey => {
        switch (permissionsKey) {
            case "all":
                return true;
            case "OWNER":
                return false;
            default:
                return config.users_groups[permissionsKey].includes(msg.getStaticUserUID());
        }
    });

if (!hasPermission && command.permissions.includes("OWNER")) {
    hasPermission = await msg.roomContext.isRoomOwnerId(msg.getStaticUserUID());
}
like image 106
5 revs, 2 users 91% Avatar answered Nov 15 '22 15:11

5 revs, 2 users 91%


I came here looking for a similar use, then found this great article presenting this manual asyncSome function: https://advancedweb.hu/how-to-use-async-functions-with-array-some-and-every-in-javascript/

It will also break the loop when a predicate returns a truthy value:

const arr = [1, 2, 3];

const asyncSome = async (arr, predicate) => {
    for (let e of arr) {
        if (await predicate(e)) return true;
    }
    return false;
};

// Example of use for this function:
const res = await asyncSome(arr, async (i) => {
    console.log(`Checking ${i}`);
    await sleep(10);
    return i % 2 === 0;
});

Applied to this use case it would simply be:

asyncSome(command.permissions, async permissionsKey => {
        switch (permissionsKey) {
            case "all": {
                return true;
            }
            case "OWNER": {
                return await msg.roomContext.isRoomOwnerId(msg.getStaticUserUID());
            }
            default: {
                return config.users_groups[permissionsKey].includes(msg.getStaticUserUID());
            }
        }
    });

It's as simple as the OP's suggested way.

like image 25
antoni Avatar answered Nov 15 '22 16:11

antoni


Transform the array into an array of Promises first, then call Promise.all on it, and check whether .some of the results array are truthy. You can avoid waiting for the whole array of Promises to resolve by checking whether .some of the items in the Promise array are truthy non-Promises, if you want:

cpermisPromises = command.permissions.map(permissionsKey => {
  switch (permissionsKey) {
    case "all":
      {
        return true;
      }
    case "OWNER":
      {
        return msg.roomContext.isRoomOwnerId(msg.getStaticUserUID());
      }
    default:
      {
        return config.users_groups[permissionsKey].includes(msg.getStaticUserUID());
      }
  }
});
if (cpermisPromises.some(result => result && typeof result.then !== 'function')) {
  // at least one was truthy, don't need to wait for async call to complete
} else {
  Promise.all(cpermisPromises).then((results) => {
    if (results.some(result => result)) {
      // at least one was truthy
    }
  });
}

Promise.all can accept arrays containing any values, but will wait for all Promise values to resolve before the Promise.all resolves.

Here's an alternative which allows you to check whether any of the synchronous values are truthy before running the Promises:

let found = false;
const promFns = [];
forloop:
for (let i = 0; i < command.permissions.length; i++) {
  const permissionsKey = command.permissions[i];
  switch (permissionsKey) {
    case "all":
      found = true;
      break forloop;
    case "OWNER":
      proms.push(() => msg.roomContext.isRoomOwnerId(msg.getStaticUserUID()));
      break;
    default:
      if (config.users_groups[permissionsKey].includes(msg.getStaticUserUID())) {
        found = true;
        break forloop;
      }
  }
}
if (found) {
  // done, at least one truthy value was found synchronously
} else {
  // need to run promises
  Promise.all(
    promFns.map(fn => fn())
  )
    .then((results) => {
      if (results.some(result => result)) {
        // done, at least one truthy value was found asynchronously
      } else {
        // no truthy value was found
      }
    });
}

Or, if want to send out requests serially, which might lead to fewer requests overall, but will take longer to complete, replace the Promise.all with:

let foundProm = false;
for (const fn of promFns) {
  if (await fn()) {
    foundProm = true;
    break;
  }
}
if (foundProm) {
  // done, at least one truthy value was found synchronously
}else {
  // no truthy value was found
}
like image 31
CertainPerformance Avatar answered Nov 15 '22 15:11

CertainPerformance