【问题标题】:How to use Array.prototype.some() with an async function?如何将 Array.prototype.some() 与异步函数一起使用?
【发布时间】:2022-04-14 17:11:02
【问题描述】:

我正在尝试执行以下操作:

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

但它总是正确的,因为 Array.prototype.some 不期望异步函数,因此在调用该函数时会返回一个 Promise。承诺是真实的。

我想知道最好的办法是将异步函数与任何 Array.prototype 函数一起使用,特别是 some 函数。

【问题讨论】:

    标签: javascript asynchronous async-await


    【解决方案1】:

    如果您希望在第一个承诺以 true 解决后立即得到结果,您也可以这样做:

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

    另类花哨的方法:

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

    不过,在您的具体情况下,由于最多有一个承诺,因此有一种更好的方法:

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

    【讨论】:

    • resolved 函数中的 resolve 不会停止父 Promise 的执行。虽然它解决得很好,但其余的 Promise 不会运行吗?
    • @JBis:你不能停止承诺的执行。实际上,执行的不是 Promise 本身。一个承诺只是持有待处理 + 完成时要做什么,已解决 + 一个值,或者被拒绝 + 一个错误。一些 Promise 库通过取消机制对此进行了扩展,但 ES Promise 没有这种机制,例如,也无法将 .then 操作与其他 Promise 分离。 (比较Promise.race。)
    • 看看this fiddle。每个承诺都会运行,即使不是所有的都需要。也许我错误地实现了该功能?这就是下面答案的问题,这里唯一的区别是它会更快地解决,对吧?
    • @JBis:当然,你知道并不是所有的任务(不是承诺——当进入像这样的细节时,记住承诺不会运行)也不需要运行只要你得到true。但是提前,你不知道是哪个,所以无论如何你都必须启动它们。一旦开始,就没有机制可以阻止它们。如果您的电话很昂贵并且可以停止,那么改进就是停止它们,是的。
    • 所以它的效率或速度,不能两全其美。好的,谢谢。
    【解决方案2】:

    我来这里是为了寻找类似的用途,然后发现这篇很棒的文章介绍了这个手动 asyncSome 函数: https://advancedweb.hu/how-to-use-async-functions-with-array-some-and-every-in-javascript/

    当谓词返回真值时,它也会中断循环:

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

    应用于这个用例,它只是:

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

    就像 OP 建议的方式一样简单。

    【讨论】:

      【解决方案3】:

      先将数组转化为Promises数组,然后对其调用Promise.all,检查结果数组的.some是否为真。如果需要,您可以通过检查 Promise 数组中的 .some 是否是真实的非 Promise 来避免等待整个 Promise 数组解析:

      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 可以接受包含 any 值的数组,但会在 Promise.all 解析之前等待所有 Promise 值解析。

      这是一种替代方法,可让您在运行 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
            }
          });
      }
      

      或者,如果想要串行发送请求,这可能会导致整体请求较少,但需要更长的时间才能完成,请将 Promise.all 替换为:

      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
      }
      

      【讨论】:

      • 虽然这会起作用,但如果其中一个 promise 解析为 true,它不会提前停止,这首先破坏了使用 some 函数的好处。
      【解决方案4】:

      你可以用map 函数包装它,像这样(我使用打字稿来澄清代码,但你可以轻松地将其转换为javascript):

       let list: any[] = [{a:1, b:2}, {a:3, b:4}];
      
       async function validator(someObject: any): Promise<boolean> {
      
           await // do some async work and 
           return // boolean expression
      
       }
      
       let someResult = (await Promise.all(list.map(validator))).some(b => b);
      

      虽然你在数组上“循环”了 2 次(在最坏的情况下),但复杂度仍然是 O(n)。

      附: every 函数也是如此

      【讨论】:

        猜你喜欢
        • 2019-08-20
        • 1970-01-01
        • 2013-08-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-22
        • 2021-11-16
        • 2018-04-16
        相关资源
        最近更新 更多