当前位置: 首页 > 知识库问答 >
问题:

异步生成器:产生被拒绝的promise

宰父飞白
2023-03-14

我一直在使用异步生成器,试图创建一个“promise排序”生成器,它接受一系列promise,并按照promise解析或拒绝的顺序逐个生成promise。比如:

async function* orderProms(prom_arr) {

    // Make a copy so the splices don't mess it up.
    const proms = [...prom_arr];

    while (proms.length) {
        // Tag each promise with it's index, so that we can remove it for the next loop.
        const {prom, index} = await Promise.race(proms.map((prom, index) => prom.then(
            () => ({prom, index}),
            () => ({prom, index})
        )));

        proms.splice(index, 1);
        yield prom;
    }
}

有了这样消耗这个发电机的想法:

const resAfter = (val, delay) => new Promise(res => setTimeout(() => res(val), delay));
const rejAfter = (val, delay) => new Promise((_, rej) => setTimeout(() => rej(val), delay));

const promises = [
    resAfter("Third", 3000),
    resAfter("First", 1000),
    rejAfter("Second", 2000), // NOTE: this one rejects!
];

(async () => {

    let ordered = orderProms(promises);

    let done = false;
    for (let next_promise = ordered.next(); !done; next_promise = ordered.next()) {
        const next = await next_promise
            .catch(err => ({done: false, value: `Caught error: ${err}`}));

        done = next.done;
        if (!done) console.log(next.value);
    }
})()

然而,我注意到,这将达到第二个promise,然后发电机将停止。这似乎是因为拒绝了“第二次”promise。当prom被拒绝时,在生成器中调用yield prom将在生成器中创建异常。

但这是我困惑的根源。我不想在这里创建异常,我只想将拒绝的promise作为迭代器结果的。我不想把它拆开。这几乎就像是被视为yield wait prom,但正如您所看到的,没有等待呼叫。

这里发生了什么,我怎么能像这个发电机一样简单地给出一个被拒绝的promise。

以下是可运行代码片段中的上述代码:

async function* orderProms(prom_arr) {

    // Make a copy so the splices don't mess it up.
    const proms = [...prom_arr];

    while (proms.length) {
        // Tag each promise with it's index, so that we can remove it for the next loop.
        const {prom, index} = await Promise.race(proms.map((prom, index) => prom.then(
            () => ({prom, index}),
            () => ({prom, index})
        )));

        proms.splice(index, 1);
        yield prom;
    }
}

const resAfter = (val, delay) => new Promise(res => setTimeout(() => res(val), delay));
const rejAfter = (val, delay) => new Promise((_, rej) => setTimeout(() => rej(val), delay));

const promises = [
    resAfter("Third", 3000),
    resAfter("First", 1000),
    rejAfter("Second", 2000), // NOTE: this one rejects!
];

(async () => {

    let ordered = orderProms(promises);

    let done = false;
    for (let next_promise = ordered.next(); !done; next_promise = ordered.next()) {
        const next = await next_promise
            .catch(err => ({done: false, value: `Caught error: ${err}`}));

        done = next.done;
        if (!done) console.log(next.value);
    }
})()

共有3个答案

赵君植
2023-03-14

我不能说公认的答案是错误的,但也不是很正确。尤其是

当出现异常时,循环停止,生成器完成。指向

部分是有问题的。

根据您的问题,虽然现代JS允许我们对这个问题做出一些优雅的处理,但按照您的要求,我们仍然可以让它工作,尽管我相信它不是。。。太好了。

第一部分-对原问题的答复

我不会进入任何细节,但只是注意到最终在生成器函数orderProms的用法,在那里事情仍然发生(生成器没有完成)后,从内部抛出异常。因此...一种方法可能是这样的;

async function* orderProms(prom_arr) {

    // Make a copy so the splices don't mess it up.
    var proms = [...prom_arr];

        // Tag each promise with it's index, so that we can remove it for the next loop.
     try {
      while (proms.length) {
            var {prom, index} = await Promise.race(proms.map((prom, index) => prom.then(
                () => ({prom, index}),
                () => ({prom, index})
            )));
            
            proms.splice(index, 1);
            yield prom;
          }
    } finally {
        proms.length && (ordered = orderProms(proms));
      }
}

var resAfter = (val, delay) => new Promise(res => setTimeout(() => res(val), delay)),
    rejAfter = (val, delay) => new Promise((_, rej) => setTimeout(() => rej(val), delay)),
    promises = [ resAfter("Third", 3000)
               , resAfter("First", 1000)
               , rejAfter("Second", 2000) // NOTE: this one rejects!
               ],
    ordered  = orderProms(promises);

async function endPoint() {
    try {
      for await (var value of ordered) {
        console.log(value)
      }
    }
    catch(e){
      console.log(`caught rejection ${e} at endpoint`);
      endPoint();
    }
}

endPoint();

第二部分:解决问题的有效方法

现在想象一下。。如果我们有一个数组,可以像处理普通数组一样填充promise,并且其中的promise会根据其解析/拒绝时间自动排序,那会怎么样。

首先,让我们扩展数组类型,并赋予它特殊的异步能力。下面的代码定义了SortedAsyncArray的类型和一个骨架。它没有经过充分的测试,但应该足以给出一个想法。再次注意finally部分,因为只有当yield由于异常或耗尽而挂起时(生成器完成情况),它才会执行。

class SortedPromisesArray extends Array {
  constructor(...args){
    super(...args);
  };
  async *[Symbol.asyncIterator]() {
    try {
      while(this.length){
        var {v,i} = await Promise.race(this.map((p,i) => p.then(v => ({v,i}))));
        this.splice(i,1);
        yield v;
      }
    } finally {
        this.length && this.splice(i,1);
    };
  };
};

那么我们应该如何使用这个异步数组呢?我提出的方法如下。

var promise  = (val, delay, resolves) => new Promise((v,x) => setTimeout(() => resolves ? v(val) : x(val), delay)),
    promises = [ promise("Third",  3000, true)
               , promise("First",  1000, true)
               , promise("Second", 2000, false) // NOTE: this one rejects!
               ],
    sortedPS = new SortedPromisesArray(...promises);

async function sink() {
  try {
    for await (let value of sortedPS){
      console.log(`Got: ${value}`);
    }
  } catch(err) {
    console.log(`caught at endpoint --> exception ${err}`);
    sink();
  };
};
sink();

第三部分正确的方法

我们在第二部分看到的只是美好和优雅,但它滥用了promise。race()。如果这不是一个异步代码,那将是一个罪恶。好在我的书中,这仍然是一种罪恶。你为什么要跑得最慢。长度多少次?

现在我们将尝试通过潜入

  • 私有类字段来隐藏那些我们不想让任何人弄乱的变量。
  • 我们甚至会检查提供的值是否是真正的promise,并扔掉那些不是promise的。尽管在检查之后,如果您愿意,您可以使用Promise.resolve(not APromise)语句来证明它们。
  • 摆脱async*[asyncIterator]()部分中的最终块。

我们魔法排序的异步阵列现在变成了

class SortedPromisesArray extends Array {
  #RESOLVE;
  #REJECT;
  #COUNT;
  constructor(...args){
    super(...args.filter(p => Object(p).constructor === Promise));
    this.#COUNT = this.length;
    this.forEach(p => p.then(v => this.#RESOLVE(v), e => this.#REJECT(e)));
  };
  async *[Symbol.asyncIterator]() {
    while(this.#COUNT--) {
      yield new Promise((resolve,reject) => ( this.#RESOLVE = resolve
                                            , this.#REJECT  = reject
                                            ));
    };
  };
};

沈()函数仅仅保持与第二部分相同。

var promise  = (val, delay, resolves) => new Promise((v,x) => setTimeout(() => resolves ? v(val) : x(val), delay)),
    promises = [ promise("Third", 3000, true)
               , promise("First", 1000, true)
               , promise("Second", 2000, false) // NOTE: this one rejects!
               ],
    sortedPS = new SPA(...promises);

async function sink() {
  try {
    for await (let value of sortedPS){
      console.log(`Got: ${value}`);
    }
  } catch(err) {
    console.log(`caught at endpoint --> exception ${err}`);
    sink();
  }
}

sink();

再一次这不是生产代码。这里只是为了演示的目的,但它是在现代JS/TS中向良好设计模式迈进的一步。

李振国
2023-03-14

你可以让promise解决类似于你从Promise.all解决得到的东西:

js prettyprint-override">async function* orderProms(prom_arr) {
    // Make a copy so the splices don't mess it up.
    const proms = new Set(prom_arr.map((prom, index) => ({prom, index})));
    while (proms.size) {
        const settled = await Promise.race(Array.from(proms, obj => obj.prom.then(
            value => Object.assign(obj, { value, status: "fulfilled" }),
            error => Object.assign(obj, { error, status: "rejected" }),
        )));
        proms.delete(settled);
        let { prom, ...rest } = settled;
        yield rest;
    }
}

const resAfter = (val, delay) => new Promise(res => setTimeout(() => res(val), delay));
const rejAfter = (val, delay) => new Promise((_, rej) => setTimeout(() => rej(val), delay));

const promises = [
    resAfter("Third", 3000),
    resAfter("First", 1000),
    rejAfter("Second", 2000), // NOTE: this one rejects!
];

(async () => {
    for await (let result of orderProms(promises)) {
        console.log(JSON.stringify(result));
    }
})().catch(err => console.log(err.message));
东方智敏
2023-03-14

这几乎像是被视为yield wait prom。这是怎么回事?

这正是异步生成器的行为方式。

我怎么能简单地从这个生成器中给出一个被拒绝的promise。

你不能。请注意,异步迭代器预计将由

try {
    for await (const value of orderProms(promises)) {
        console.log(value);
    }
} catch(err) {
    console.error('Caught error: ', err);
}

语法中没有单独错误处理的便利。当出现异常时,循环停止,生成器完成。点。

那你能做什么呢?我看到三种选择:

>

  • 保持原样,将早期失败视为功能(类似于Promise.all
  • 处理错误(无论是在orderProms中还是在将promise传递给它之前),并产生promise状态和值的元组

    for await (const value of orderProms(promises.map(prom =>
        prom.catch(err => `Caught error: ${err}`)
    ))) {
        console.log(value);
    }
    

  •  类似资料:
    • 我正在研究ES9中提出的异步生成器,以便能够掌握逻辑。 因为每个产生的值都被用作返回对象中的值属性——由Promise包装——来自下一个()调用: 我认为这对我的promise也是正确的: 因此,这些promise被合并在一起。这听起来并不坏,但我并不认为这是因为: 那么,关键字wait与关键字yield在同一个表达式中使用完全无用,还是我遗漏了一些重要的内容?

    • 问题内容: 承诺和生成器允许您编写异步代码。我不明白为什么在ECMA脚本6中同时引入了这两种机制。什么时候最好使用Promise?什么时候使用生成器? 问题答案: 这两种技术之间没有对立。它们共存并很好地互补。承诺使您能够获得尚不可用的异步操作的结果。它解决了厄运金字塔问题。所以代替: 你可以写: 但是即使有承诺,您也必须以异步方式编写代码- 您必须始终将回调传递给函数。编写异步代码比同步代码难得

    • 本文向大家介绍Node.js中的异步生成器与异步迭代详解,包括了Node.js中的异步生成器与异步迭代详解的使用技巧和注意事项,需要的朋友参考一下 前言 生成器函数在 JavaScript 中的出现早于引入 async/await,这意味着在创建异步生成器(始终返回 Promise 且可以 await 的生成器)的同时,还引入了许多需要注意的事项。 今天,我们将研究异步生成器及其近亲——异步迭代。

    • 我正在使用Spring 5网络流量、SpringBoot 2和Reactive MongoDB构建一个POC。在使用maven插件构建项目时,我得到了如下错误MongoDB连接错误:- 以下是我的申请表。属性文件:- 波姆。xml:- 请告知这里可能存在什么问题?我是否需要在我的机器上显式安装MongoDB并在运行此项目之前运行它。我相信MongoDB实例将自行运行,因为SpringBoot将在运

    • 我创建了一个jenkins服务器,并试图在该服务器上构建一个。NET Core2.0.0项目。我已经能够成功地从源代码管理中提取并在工作区中存储源文件。但是,我在运行dotnet构建命令时遇到了一个问题。这就是我得到的。 /usr/share/dotnet/sdk/2.0.0/microsoft.common.currentversion.targets(4116,5):错误MSB3021:无法将

    • 我在研究javascript生成器,发现这个实现使用递归函数模拟异步等待的效果。我想知道我们是否可以实现类似但非递归的东西?我思考了很长时间,但没有找到有效的解决方案。