我有一个分页的API,我需要自动获取每一页的结果。
我构建了以下递归承诺链,它实际上为我提供了所需的输出。我已经将传统的决心重命名为outer,试图让我的头脑更加清醒,但我仍然对如何将outer函数传递到嵌套中感到困惑。那么,是神奇的酱汁让它发挥作用吗?
我以为我很了解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));
});
}
TL:DR
当你通过递归添加越来越多的Promise时,它们就像洋葱皮一样堆积起来。当每一个都被解析并调用.then()
时,Promises将从内到外删除,直到一个都没有。
承诺链接
在Resolve处理程序中创建Promise对象时在另一个承诺中,两个承诺都是按照由内而外的顺序解决的。此外,当链接通过Promise实例方法(.then()
、.catch()
、.always()
等(,链接方法具有优先级在最外层Promise对象解析之前执行。
也许在这里解释得更好。
您的代码在外部构造的Promise对象的Resolve处理程序中创建Axios Promise。这个AxiosPromise的.then()
将在外部Promise最终结算之前执行。之后,结果通过外部Promise对象传递,而不进行修改或处理。基本上是一个无操作。
这就是为什么包装器Promise(显式构造反模式(是不必要的,也是令人沮丧的——这是对时间和资源的浪费,在这个代码中没有任何好处。
随着递归的混合,Promise对象不断堆积
因此(目前(从.execute()
方法返回了对外部Promise对象的引用。但何时/如何解决(解决或拒绝(?
- Axios Promise通过AJAX结果解决
- 如果这是一个递归调用,那么这就是一个内部Promise(即使是用
.then(outer)
创建的,在这种情况下更容易混淆(
- 如果这是一个递归调用,那么这就是一个内部Promise(即使是用
- 使用AJAX结果调用最外层的
.then()
(或有理由拒绝( - 使用结果更新实例变量
pagedListResponses
- 如果
res.headers.link == true && pageObject.last == true
- 重复&从1<-开始待定有未结算的承诺
- 否则如果
res.headers.link == true && pageObject.last == false
- 用
pageListResponses
<-完全解决
- 用
- 否则,如果
res.headers.link == false && pageObject.last == false
- 用"未找到寻呼标头"<-完全解决
- 然后用
pageListResponses
调用附加到对execute()
的初始调用的then()
方法- 例如。
this.execute({...}).then(pageList=>doSomethingWithPageOfResults());
- 例如。
因此,使用.then()
中游的链接允许我们在返回最终解决了Promise的结果。
在递归代码中:
this.execute<T>({
url: pagingObject.next,
}).then(outer);
这里的.then()
调用只是将一个新的内部Promise添加到链中,正如您所意识到的,这与编写完全相同
.then(result=>outer(result));
参考
异步/等待
最后,建议使用Async/Await。强烈建议使用Async/Await重写Promise代码,用于复杂的场景(或者说任何(。尽管仍然是异步的,但这使得阅读和合理化代码序列变得更加容易。
您的代码被重写以利用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/catch
的使用。catch
块处理嵌套/链中的任何拒绝。您可能会注意到递归调用没有await
关键字。在这种情况下没有必要,因为这是一个只传递其结果的Promise。(在这里添加await
是可以的,但没有真正的好处(。
此代码在外部的使用方式相同:this.execute({...}).then(pageList=>doSomethingWithPageOfResults());
让我知道你可能还有什么差距。
当您调用this.execute
时,它会返回一个Promise<AxiosResponse | T>
。
因此,通过调用.then(outer)
,递归execute
函数的响应将用于解析外部promise。
它基本上与this.execute(...).then(response => outer(response))
相同,如果这让它更清晰的话。