【问题标题】:Recursive JavaScript Promises (auto paging an API)递归 JavaScript 承诺(自动分页 API)
【发布时间】:2022-01-04 21:09:35
【问题描述】:

我有一个分页 API,需要自动获取每页结果。

我已经构建了以下递归承诺链,它实际上给了我想要的输出。我已将传统的决心重命名为外部,以尝试将我的头更多地包裹起来,但我仍然对如何将外部函数传递到嵌套的 .then 感到困惑。那么是允许它工作的魔法酱吗?

我以为我很了解 Promises,但显然不是!

  protected execute<T>(httpOptions): Promise<AxiosResponse | T> {
    const reqObject: HttpConfig = {
      ...httpOptions,
      baseURL: this.config.baseUri,
      headers: { Authorization: `Bearer ${this.config.apiKey}` },
    };

    return new Promise((outer, reject) => {
      axios
        .request<T>(reqObject)
        .then((res) => {
          if (res.headers.link) {
            const pagingObject: PagingObject = this.getPagingObject(res);
            this.pagedListResponses = this.pagedListResponses.concat(res.data);
            if (pagingObject.last) {
              this.execute<T>({
                url: pagingObject.next,
              }).then(outer);
            } else {
              outer(this.pagedListResponses as any);
            }
          } else {
            outer("No paging header found");
          }
        })
        .catch((err) => reject(err));
    });
  }

【问题讨论】:

  • 你需要所有页面一次,还是滚动时等等?
  • 您的方法似乎永远不会初始化this.pagedListResponses,如果您多次调用execute,这将成为错误的来源。请改用局部变量。
  • 在该代码所在类的构造函数中初始化。
  • 对我的回答有任何疑问吗?

标签: javascript typescript recursion es6-promise


【解决方案1】:

TL:DR

随着您通过递归添加越来越多的 Promise,它们会像洋葱皮一样堆积起来。当每个都解决并调用.then() 时,Promise 会从内到外被删除,直到没有剩余。

Promise Chaining

Resolve 处理程序中创建 Promise 对象时 另一个 Promise 的两个 Promise 都以由内而外的顺序结算。此外,当 chaining 通过 Promise 实例方法(.then().catch()、.always() 等),链接方法优先且是 在最外层的 Promise 对象解析之前执行。

也许更好地解释here

您的代码在外部构造的 Promise 对象的 Resolve 处理程序中创建 Axios Promise。这 AxiosPromise 的 .then() 将在外部 Promise 最终稳定之前执行。之后,结果将通过外部 Promise 对象传递,无需修改或处理。基本上是一个无操作

这就是包装器 Promise (explicit-construction anti-pattern) 的原因 是不必要且不鼓励的 - 这是浪费时间和资源,在此代码中没有任何好处。

混合使用递归,Promise 对象不断堆积。

因此(目前)从.execute() 方法返回对该外部 Promise 对象的引用。但何时/如何解决(解决或拒绝)?

  1. Axios Promise 使用 AJAX 结果解析
    • 如果这是一个递归调用,这是一个内部 Promise(即使使用 .then(outer) 创建,在这种情况下更容易混淆)
  2. 最外层的.then() 被AJAX 结果调用(或被拒绝)
  3. 实例变量pagedListResponses随结果更新
  4. 如果res.headers.link == true &amp;&amp; pageObject.last == true
    • 递归并从 1 开始 pending 未解决的 Promise(s)
  5. 否则如果res.headers.link == true &amp;&amp; pageObject.last == false
    • pageListResponses解决最外面的Promise 解决
  6. 否则如果res.headers.link == false &amp;&amp; pageObject.last == false
    • 用“未找到分页头”解决最外层的 Promise 解决
  7. 然后使用pageListResponses 调用附加到初始调用execute()then() 方法
    • 例如。 this.execute({...}).then(pageList=&gt;doSomethingWithPageOfResults());

所以链接,使用.then() midstream,允许我们在返回之前做一些数据处理(就像你所做的那样) 最终解决了 Promise 结果。

在递归代码中:

this.execute<T>({
    url: pagingObject.next,
}).then(outer);

这里的.then() 调用只是向链中添加了一个新的内部 Promise,正如您所意识到的,它与编写完全相同:

.then(result=&gt;outer(result));Reference

Async/Await

最后,推荐使用Async / Await。强烈建议Rewrite Promise code with Async/Await 用于复杂(或有人说任何)场景。 尽管仍然是异步的,但这使得阅读和合理化代码序列要容易得多

重写您的代码以利用 Async/Await:

 protected async execute<T>(httpOptions): Promise<T> {
    const reqObject = {
      ...httpOptions,
      baseURL: this.config.baseUri,
      headers: {Authorization: `Bearer ${this.config.apiKey}`},
    };
    try {
      const res: AxiosResponse<T> = await axios.request<T>(reqObject);
      if (res) {
        const pagingObject: boolean = this.getPagingObject(res);
        this.pagedListResponses = this.pagedListResponses.concat(res.data);
        if (pagingObject) {
          return this.execute<T>({ url: 'https://jsonplaceholder.typicode.com/users/1/todos'});
        } else {
          return this.pagedListResponses as any;
        }
      } else {
        return "No paging header found" as any;
      }
    } catch (err) {
      throw new Error('Unable to complete request for data.');
    }
  }

这里,async 直接表示“此方法返回一个 Promise”(按类型输入)。 await 关键字仅在代码中使用一次以等待解析 的 AJAX 请求。该语句返回实际结果(按类型)而不是 Promise 对象。网络错误会发生什么?注意这里使用try/catchcatch 块处理嵌套/链中的任何拒绝。您可能会注意到递归调用没有 await 关键字。在这种情况下没有必要,因为它是一个仅传递其结果的 Promise。 (在这里添加await 是可以的,但它没有真正的好处)。

此代码在外部的使用方式相同: this.execute({...}).then(pageList=&gt;doSomethingWithPageOfResults());

让我知道您可能还有哪些差距。

【讨论】:

    【解决方案2】:

    当您调用this.execute 时,它会返回一个Promise&lt;AxiosResponse | T&gt;

    因此,通过调用.then(outer),来自递归execute 函数的响应将用于解决您的外部承诺。

    它与this.execute(...).then(response =&gt; outer(response)) 基本相同,如果这样更清楚的话。

    【讨论】:

      猜你喜欢
      • 2021-06-30
      • 1970-01-01
      • 2015-05-15
      • 1970-01-01
      • 1970-01-01
      • 2014-02-04
      • 1970-01-01
      • 2017-12-18
      • 1970-01-01
      相关资源
      最近更新 更多