javascript 在Array.find()中使用异步函数

ercv8c1e  于 2023-03-11  发布在  Java
关注(0)|答案(6)|浏览(278)

看起来我不能使用异步函数作为Array.find()的第一个参数,我不明白为什么这段代码不能工作,这是幕后发生的事情?

function returnsPromise() {
  return new Promise(resolve => resolve("done"));
}

async function findThing() {
  const promiseReturn = await returnsPromise();
  return promiseReturn;
}

async function run() {
  const arr = [1, 2];
  const found = await arr.find(async thing => {
    const ret = await findThing();
    console.log("runs once", thing);
    return false;
  });
  console.log("doesn't wait");
}

run();

https://codesandbox.io/s/zk8ny3ol03

dtcbnfnu

dtcbnfnu1#

简单地说,find并不期望返回promise,因为它不适用于异步操作。它循环遍历数组,直到其中一个元素返回一个truthy值。一个对象,包括promise对象,是truthy,所以find在第一个元素上停止。
如果你想要一个异步的find,你需要自己编写,你需要考虑的一个问题是你是想并行运行,还是想顺序运行,在进入下一个索引之前阻塞。
例如,这里有一个版本,它并行运行所有的承诺,然后一旦所有的承诺都得到解决,它会找到第一个产生真实值的承诺。

async function findAsync(arr, asyncCallback) {
  const promises = arr.map(asyncCallback);
  const results = await Promise.all(promises);
  const index = results.findIndex(result => result);
  return arr[index];
}

//... to be used like:

findAsync(arr, async (thing) => {
  const ret = await findThing();
  return false;
})
kdfy810k

kdfy810k2#

以下是按顺序运行的TypeScript版本:

async function findAsyncSequential<T>(
  array: T[],
  predicate: (t: T) => Promise<boolean>,
): Promise<T | undefined> {
  for (const t of array) {
    if (await predicate(t)) {
      return t;
    }
  }
  return undefined;
}
eblbsuwk

eblbsuwk3#

注意Array.prototype.filter是同步的,所以它不支持异步行为。我认为这同样适用于“find”属性。您可以随时定义自己的异步属性:)希望这对您有所帮助!

vecaoik1

vecaoik14#

其他答案提供了两种解决问题的方法:
1.并行运行承诺,并在返回索引之前等待所有承诺
1.按顺序运行的承诺,所以等待每一个单一的承诺,然后再移动到下一个。
假设你有五个承诺,它们在不同的时间完成:第一个在一秒后,第二个在两秒后,等等......第五个在五秒后。
如果我在找三秒后结束的那个:
1.第一个解决方案将等待5秒,直到所有承诺都得到解决。然后,它将查找匹配的解决方案。
1.第二个将在返回之前评估前三个匹配项(1 + 2 + 3 =6秒)。
下面是第三种选择,通常会更快:并行运行这些承诺,但只等到第一场比赛(“与它们赛跑”):3秒

function asyncFind(array, findFunction) {
  return new Promise(resolve => {
    let i = 0;
    array.forEach(async item => {
      if (await findFunction(await item)) {
        resolve(item);
        return;
      }
      i++;
      if (array.length == i) {
        resolve(undefined);
      }
    });
  });
}

//can be used either when the array contains promises
var arr = [asyncFunction(), asyncFunction2()];
await asyncFind(arr, item => item == 3);

//or when the find function is async (or both)
var arr = [1, 2, 3];
await asyncFind(arr, async item => {
    return await doSomething(item);
}

当寻找一个不存在的项目时,解决方案1和3将花费相同的时间(直到所有承诺都被评估,这里是5秒),顺序方法(解决方案2)将花费1+2+3+4+5 = 15秒。
演示:https://jsfiddle.net/Bjoeni/w4ayh0bp

zbsbpyhn

zbsbpyhn5#

我提出了一个解决方案,这里似乎没有涉及,所以我想分享一下。

  • 接受async函数(或返回承诺的函数)
  • 向函数提供itemindexarray
  • 返回具有 * 最快 * 解析承诺的项(所有项并行计算)
  • 如果async函数拒绝(抛出错误),提前退出
  • 支持泛型类型(在TypeScript中)

基于这些需求,我提出了下面使用Promise.any的解决方案,它将使用第一个满足的值解决问题,如果没有一个满足,则使用AggregateError拒绝。

type Predicate<T> = (item: T, index: number, items: T[]) => Promise<boolean>

export const any = async <T>(array: T[], predicate: Predicate<T>): Promise<T> => {
  return Promise.any(
    array.map(async (item, index, items) => {
      if (await predicate(item, index, items)) {
        return item
      }

      throw new Error()
    })
  )
}

现在我们可以用async函数并行搜索数组,并返回“最快”的解析结果。

const things = [{ id: 0, ... }, { id: 1, ... }]
const found = await any(things, async (thing) => {
  const otherThing = await getOtherThing()

  return thing.id === otherThing.id
})

按顺序枚举数组

如果我们想枚举数组 in sequence,就像Array.find那样,那么我们可以做一些修改。

export const first = async <T>(array: T[], predicate: Predicate<T>): Promise<T> => {
  for (const [index, item] of array.entries()) {
    try {
      if (await predicate(item, index, array)) {
        return item
      }
    } catch {
      // If we encounter an error, keep searching.
    }
  }

  // If we do not find any matches, "reject" by raising an error.
  throw new Error()
}

现在,我们可以搜索数组 in sequence 并返回 first 解析结果。

const things = [{ id: 0, ... }, { id: 1, ... }]
const found = await first(things, async (thing) => {
  const otherThing = await getOtherThing()

  return thing.id === otherThing.id
})
sc4hvdpw

sc4hvdpw6#

到目前为止,所有建议都缺少find的第二个参数,因此我提供两个进一步的建议:

Array.prototype.asyncFind = function (predicate, thisArg=null) {
    return Promise.any(this.map(async (value, index, array) =>
        new Promise(async (resolve, reject) => 
            await predicate.bind(thisArg)(value, index, array) ? resolve(value) : reject()
        )
    )).catch(() => undefined)
}

第一个函数并行运行,并返回(就像原始函数一样)满足提供的测试函数的第一个值(相对于time),如果没有找到,则返回undefined。

Array.prototype.asyncFind = async function (predicate, thisArg=null) {
    const boundPredicate = predicate.bind(thisArg)

    for (const key of this.keys()) {
        if (await boundPredicate(this[key], key, this)) return this[key]
    }

    return undefined
}

第二个函数一个接一个地运行(考虑排序),并返回(就像原始函数一样)满足提供的测试函数的第一个值(相对于sorting),如果没有找到,则返回undefined。

相关问题