Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/441.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript RxJs flatMapLatest/switchMap取消回调。onCancel()在哪里?_Javascript_Reactive Programming_Rxjs_Observable - Fatal编程技术网

Javascript RxJs flatMapLatest/switchMap取消回调。onCancel()在哪里?

Javascript RxJs flatMapLatest/switchMap取消回调。onCancel()在哪里?,javascript,reactive-programming,rxjs,observable,Javascript,Reactive Programming,Rxjs,Observable,我有两个嵌套的可观察流,它们执行HTTP请求。现在我想显示一个加载指示器,但无法使其正常工作 var pageStream = Rx.createObservableFunction(_self, 'nextPage') .startWith(1) .do(function(pageNumber) { pendingRequests++; }) .concatMap(function(pageNumber)

我有两个嵌套的可观察流,它们执行HTTP请求。现在我想显示一个加载指示器,但无法使其正常工作

var pageStream = Rx.createObservableFunction(_self, 'nextPage')
        .startWith(1)
        .do(function(pageNumber) {
            pendingRequests++;
        })
        .concatMap(function(pageNumber) {
            return MyHTTPService.getPage(pageNumber);
        })
        .do(function(response) {
            pendingRequests--;
        });

Rx.createObservableFunction(_self, 'search')
        .flatMapLatest(function(e) {
            return pageStream;
        })
        .subscribe();



search();
nextPage(2);
nextPage(3);
search();
这将触发
pendingRequests++
4次,但
pendingRequests--
仅触发一次,因为
flatMapLatest
将在前3个HTTP响应到达之前取消内部可观察到的内容

我找不到类似于
onCancel
回调的任何东西。我还尝试了
onCompleted
onError
,但是
flatMapLatest
不会触发这些错误

有没有其他办法让它工作

谢谢大家!

编辑:所需的加载指示器行为
  • 示例:单个
    search()
    调用

    • 搜索()->开始加载指示器
    • 返回search()响应时->禁用加载指示器
  • 示例:
    search()
    nextPage()
    调用。(在返回search()响应之前调用nextPage()

    • 搜索()->开始加载指示器
    • nextPage()->指示器已启动,但此处无需执行任何操作
    • 两个响应到达后停止加载指示器
  • 示例:
    search()
    search()
    。(search()调用相互覆盖,但第一个调用的响应可以被忽略)

    • 搜索()->开始加载指示器
    • search()->指示器已启动,但此处无需执行任何操作
    • 当第二次搜索()的响应到达时停止加载指示器
  • 示例:
    search()
    nextPage()
    search()
    。(同样:由于第二次搜索(),可以忽略来自上一次搜索()和下一次搜索()的响应)

    • 搜索()->开始加载指示器
    • nextPage()->指示器已启动,但此处无需执行任何操作
    • search()->指示器已启动,但此处无需执行任何操作
    • 当第二次搜索()的响应到达时停止加载指示器
  • 示例:
    search()
    nextPage()
    。但这次在search()响应返回后调用nextPage()

    • 搜索()->开始加载指示器
    • 停止加载指示器,因为已到达search()响应
    • 下一页()->开始加载指示器
    • 停止加载指示器,因为下一页()响应已到达

  • 我尝试使用
    pendingRequests
    计数器,因为我可以同时有多个相关的请求(例如:
    search()、nextPage()、nextPage()
    )。当然,我想在所有相关请求完成后禁用加载指示器


    调用
    search(),search()
    时,第一个search()是不相关的。这同样适用于
    search()、nextPage()、search()
    。在这两种情况下,只有一个活动的相关请求(最后一个
    search()
    )。

    一种方法:使用finally运算符(,)。无论何时observable被取消订阅或出于任何原因完成,Finally都会触发

    我还将计数器逻辑移到concatMap函数内部,因为您实际上是在计算getPage请求,而不是已通过的值的数量。这是一个微妙的区别

    var pageStream = Rx.createObservableFunction(_self, 'nextPage')
            .startWith(1)
            .concatMap(function(pageNumber) {
                ++pendingRequests;
                // assumes getPage returns an Observable and not a Promise
                return MyHTTPService.getPage(pageNumber)
                   .finally(function () { --pendingRequests; })
            });
    

    使用
    switchMap
    aka
    flatMapLatest
    可以在新的外部项到达时尽快修剪当前内部流的执行。这当然是一个好的设计决策,否则会带来很多混乱,并允许一些可怕的行为。如果你真的想做点什么
    onCancel
    ,你可以通过自定义
    unsubscribe
    callback创建你自己的可观察对象。但我仍然建议不要将
    取消订阅
    与外部环境的变化状态结合起来。理想情况下,
    取消订阅
    只会清理内部使用的资源

    然而,您的特殊情况可以在不访问
    onCancel
    或类似程序的情况下解决。关键的观察结果是——如果我正确理解了您的用例——在
    search
    上,所有以前的/挂起的操作都可能被忽略。因此,不用担心计数器的递减,我们可以简单地从1开始计数

    关于该片段的一些评论:

    • 使用
      BehaviorSubject
      计算挂起的请求,因为它已准备好与其他流组合
    • 检查您在问题中发布的所有案例,这些案例都有效
    • 增加了一些模糊测试来证明正确性
    • 不确定在
      搜索仍处于挂起状态时是否允许
      nextPage
      ——但似乎只是使用
      concatMapTo
      vs
      merge
      的问题
    • 仅使用标准
      Rx
      操作员

    console.clear();
    const searchSub=new Rx.Subject();//触发搜索
    const nextPageSub=new Rx.Subject();//触发器下页
    const pendingSub=new Rx.BehaviorSubject();//统计挂起的请求数
    const randDurationFactory=min=>max=>()=>Math.random()*(max-min)+min;
    const randDuration=randDurationFactory(250)(750);
    const addToPending=n=>()=>pendinsub.next(pendinsub.value+n);
    const inc=addToPending(1);
    const dec=addToPending(-1);
    常数fakeSearch=(x)=>Rx.可观察到的(x)
    .do(()=>console.log(`SEARCH-START:${x}`)
    .flatMap(()=>
    Rx.Observable.timer(randDuration())
    .do(()=>console.log(`SEARCH-SUCCESS:${x}`)))
    常数fakeNextPage=(x)=>Rx.Observable.of(x)
    .do(()=>console.log(`NEXT-PAGE-START:${x}`)
    .flatMap(()=>
    Rx.Observable.timer(randDuration())
    .do(()=>console.log(`NEXT-PAGE-SUCCESS:${x}`)))
    //订阅
    搜索潜艇
    .do(()=>console.warn('NEW_SEARCH'))
    .do(()=>pendingSub.next(1))
    
    function getInstance() {
     return {
        loaderVisible: false,
        reqStack: [],
    
        requestStart: function (req){
            console.log('%s%s req start', req.type, req.id)
            if(_.filter(this.reqStack, r => r.done == false).length > 0 && !this.loaderVisible){
                this.loaderVisible = true
                console.log('loader visible')
            }
        },
    
        requestEnd: function (req, body, delay){
            console.log('%s%s req end (took %sms), body: %s', req.type, req.id, delay, body)
            if(req === this.reqStack[this.reqStack.length-1] && req.type == 'search'){
                this.hideLoader(req)
                return true
            } else if(_.filter(this.reqStack, r => r.done == true).length == this.reqStack.length && this.loaderVisible){
                this.hideLoader(req)
                return true
            } 
            return false
        },
    
        hideLoader: function(req){
            this.loaderVisible = false
            console.log('loader hidden (after %s%s request)', req.type, req.id)
        },
    
        getPage: function (req, delay) {
            this.requestStart(req)
            return Rx.Observable
                    .fromPromise(Promise.resolve("<body>" + Math.random() + "</body>"))
                    .delay(delay)
        },
    
        search: function (id, delay){
            var req = {id: id, done: false, type: 'search'}
            this.reqStack.push(req)
            return this.getPage(req, delay).map(body => {  
                        _.find(this.reqStack, r => r.id == id && r.type == 'search').done = true
                        return this.requestEnd(req, body, delay)
                    })
        },
    
        nextPage: function (id, delay){
            var req = {id: id, done: false, type: 'nextPage'}
            this.reqStack.push(req)
            return this.getPage(req, delay).map(body => {  
                        _.find(this.reqStack, r => r.id == id && r.type == 'nextPage').done = true
                        return this.requestEnd(req, body, delay)
                    })
        },
    }
    }
    
    describe('animation loader test:', function() {
    
        var sut
    
        beforeEach(function() {
            sut = getInstance()
        })
    
        it('search', function (done) {
            sut.search('1', 10).subscribe(expectDidHideLoader)
            testDone(done)
        })
    
        it('search, nextPage', function (done) {
            sut.search('1', 50).subscribe(expectDidHideLoader)
            sut.nextPage('1', 20).subscribe(expectDidNOTHideLoader)
            testDone(done)
        })
    
        it('search, nextPage, nextPage', function(done) {
            sut.search('1', 50).subscribe(expectDidHideLoader)
            sut.nextPage('1', 40).subscribe(expectDidNOTHideLoader)
            sut.nextPage('2', 30).subscribe(expectDidNOTHideLoader)
            testDone(done)
        })
    
        it('search, nextPage, nextPage - reverse', function(done) {
            sut.search('1', 30).subscribe(expectDidNOTHideLoader)
            sut.nextPage('1', 40).subscribe(expectDidNOTHideLoader)
            sut.nextPage('2', 50).subscribe(expectDidHideLoader)
            testDone(done)
        })
    
        it('search, search', function (done) {
            sut.search('1', 60).subscribe(expectDidNOTHideLoader) //even if it takes more time than search2
            sut.search('2', 50).subscribe(expectDidHideLoader)
            testDone(done)
        })
    
        it('search, search - reverse', function (done) {
            sut.search('1', 40).subscribe(expectDidNOTHideLoader) 
            sut.search('2', 50).subscribe(expectDidHideLoader)
            testDone(done)
        })
    
        it('search, nextPage, search', function (done) {
            sut.search('1', 40).subscribe(expectDidNOTHideLoader) //even if it takes more time than search2
            sut.nextPage('1', 30).subscribe(expectDidNOTHideLoader) //even if it takes more time than search2
            sut.search('2', 10).subscribe(expectDidHideLoader)
            testDone(done)
        })
    
        it('search, nextPage (call after response from search)', function (done) {
            sut.search('1', 10).subscribe(result => {
                expectDidHideLoader(result)
                sut.nextPage('1', 10).subscribe(expectDidHideLoader)
            })
            testDone(done)   
        })
    
        function expectDidNOTHideLoader(result){
            expect(result).to.be.false
        }
    
        function expectDidHideLoader(result){
            expect(result).to.be.true
        }
    
        function testDone(done){
            setTimeout(function(){
                done()
            }, 200)
        }
    
    })
    
    var hasPending: Subject<boolean> = BehaviorSubject(false);
    
    var pageStream = Rx.createObservableFunction(_self, 'nextPage')
        .startWith(1)
        .do(function(pageNumber) {
            hasPending.next(true);
        })
        .concatMap(function(pageNumber) {
            return MyHTTPService.getPage(pageNumber);
        })
        .do(function(response) {
            hasPending.next(false);
        });
    
    var hasPending: booolean = false;
    
    hasPending = true;
    
    hasPending = false;
    
    .do(function(response) {
            pendingRequests = 0;
        });