取消异步/等待

sai*_*ran 5 javascript async-await es6-promise angular6

我有一个像下面这样的功能

function async populateInventories(custID){
    this.inventories = await this.inventoryService.getCustomerInventories(custID)

    //do some stuff with Inventories

    // another await 

}
Run Code Online (Sandbox Code Playgroud)

我的问题是我需要取消一个 Ongoing Await 请求,并像我们使用订阅一样发送一个 Fresh Await 请求。

以前我的功能如下所示,我可以取消以前的订阅:

function async populateInventories(custID){

    let invetroySubscription$;

    if(invetroySubscription$!==undefined){
        invetroySubscription$.unsubscribe();
    }

    invetroySubscription$=this.inventoryService.getCustomerInventories(custID).subscribe((data:any)=>{
        this.inventories = data.response;

        //do some stuff with Inventories


         // another subscription here -- I was told this way of doing is wrong

    })

}
Run Code Online (Sandbox Code Playgroud)

使用 async/await,我能够按顺序处理任何操作,这真的很棒,并且代码非常易读,我无法使用订阅来实现,但是 async/await 有自己的缺点。

我可以一次停止多次调用来填充库存以避免我的问题,但我正在寻找是否可以获得更多解决方案(整洁)来解决我的问题。

PS:我读到在订阅调用或嵌套订阅中调用另一个订阅不是一个好的做法。

Chr*_*isY 5

处理异步操作的取消取决于我们执行异步操作的类型方式。

我假设我们的异步操作的类型是 HTTP 请求。因此,我们有多种选择来执行实际的 HTTP 请求:

  1. 使用Angular 本身的HttpClient (最常见的情况和最佳实践!
  2. 使用Axios(据我所知是 Fetch API 的包装器)
  3. 使用浏览器本身提供的Fetch API

Http客户端

当使用HttpClientAngular 本身的服务时,建议将其RxJs结合使用。因此,对于您的情况,我会切换回您上面描述的 Observable 解决方案。为什么?:

  • HttpClient 本身使用RxJs并返回 Observables 作为默认值
  • RxJs与switchMap操作符结合使用HttpClient可以“轻松”取消正在进行的请求
  • 请注意,switchMap本身不会取消 HTTP 请求或保留其逻辑。它只是对内部流的订阅调用取消订阅。请求取消逻辑由HttpClient的实现(或多或少)提供,它在取消订阅回调中返回取消逻辑。
  • 不幸的是,编写正确的RxJs代码并避免多重或嵌套订阅并不那么容易。在你的情况下,我会尝试创建一个(我假设)点击事件流并使用:
    • switchMap来展平流(相当于flatMap)并取消订阅现有的内部流,以防发生新的点击事件。
    • concatMapinventoriesRequest在创建之前等待流完成populateInventoriesRequest
 this.populateInventoryClicks$
  .pipe(
    switchMap(() => this.inventoryService.inventoriesRequest()),
    concatMap(inventories => this.inventoryService.populateInventoriesRequest(inventories)),
  )
  .subscribe(response => {
    console.log(response);
});
Run Code Online (Sandbox Code Playgroud)

请看一下Stackblitz示例。在开发控制台的网络选项卡中限制速度。在“网络”选项卡中,我们应该看到,在单击多个按钮时,仅完成最后一个请求,并且之前的所有请求都被取消。 在此输入图像描述


阿克西奥斯

在使用axios客户端时,我们可以使用cancel它为我们提供的方法。如果您对他们的方法感兴趣,请查看他们的文档。请注意,当我们使用 Axios 时, Angular 中的HttpInterceptors不会对这些请求运行。这是我知道没有人将它与 Angular 结合使用的最明显原因。


获取API

Fetch API 还为我们提供了中止正在进行的请求的可能性fetch。我不能推荐这种方法,原因与我不能推荐 Axios 方法的原因相同。但如果你有兴趣的话就看看吧。


小智 0

您可以尝试在 getCustomerInventories 函数内部添加某种回调,以便在您希望取消承诺时解析或拒绝承诺。例如,如果您希望它停止并根据时间发送新的,您可以添加类似的内容

setTimeout(resolve, ms);
Run Code Online (Sandbox Code Playgroud)

到它。

您可以使用Promise.race()来检索原始的等待或超时或其他任何内容。就像是:

//keepGoing and checkGoingFunction could be whatever you need them to be but something that returns a promise that will resolve or reject when you want to "cancel" the await
var keepGoing = true;
var checkGoingFunction = function(){
    return new Promise((resolve, reject) => {
        while(keepGoing){
          //just filling time and keeping a loop, maybe not efficient but working
        }
        //after it exits (when you change keepGoing to false)
        resolve("timedOut");
    });
}

function async populateInventories(custID){
    //a promise returned by your method
    var custInvPromise = this.inventoryService.getCustomerInventories(custID)
    var timeoutPromise = checkGoingFunction(); //promise from timeout function
    var bigPromise = Promise.race(custInvPromise, timeoutPromise);
    //maybe add setTimeout(function(){keepGoing = false}, 5000); if you wanted to timeout after 5 seconds

    var result = await bigPromise;
    if(result == "timedOut"){
       //resend await or whatever you need to do
    } else {
        this.inventories = result;
        //do some stuff with Inventories
    }




}
Run Code Online (Sandbox Code Playgroud)