V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yxcoder
V2EX  ›  程序员

JS 多次请求 如何使后者覆盖前者

  •  
  •   yxcoder · 113 天前 · 2480 次点击
    这是一个创建于 113 天前的主题,其中的信息可能已经有所发展或是发生改变。

    多个 AJAX 请求,分别为 N1,N2,N3,N4,N5

    N1 请求完成之后输出 console.log(1) N2 请求完成之后输出 console.log(2) 依此类推

    如果 N1,N2,N3,N4,N5 依次执行,但是返回时间不确定,如何使最终输出的只有 5

    这里的请求数量可能会很多,五个只是举例。 前者的请求无法取消

    第 1 条附言  ·  113 天前
    问题已解决,乐观锁,或者使用时间戳即可,可以参看 2 楼和 13 楼的解答


    场景大致如下:
    商品列表,用户可能会不停的切换点击商品(商品互斥),每个商品的价格不一样,需要单独发请求获取,所以展示出来的价格希望是最后一次用户选中的商品价格,同时为了优化体验,商品价格请求是,希望展示的文案是 ”价格查询中...“(也就是前面的请求不能对价格字段造成任何影响)
    mxT52CRuqR6o5
        1
    mxT52CRuqR6o5  
       113 天前
    简单一点的,保存上一个的 abortController ,下一次请求发出去前把上一个请求 abort 掉
    复杂一点的,rxjs
    westoy
        2
    westoy  
       113 天前   ❤️ 1
    一般数据都会记录一个 updated_at 的

    返回的时候和之前那次比较, 取新的
    Kenmin
        3
    Kenmin  
       113 天前
    善用 async await
    doommm
        4
    doommm  
       113 天前   ❤️ 1
    我选 rxjs
    ifbluethen
        5
    ifbluethen  
       113 天前
    for(var c = 0; c < 5; c++) {
    setTimeout(function() {
    console.log(c);
    }, 1000);
    }
    kop1989smurf
        6
    kop1989smurf  
       113 天前
    不懂这个“依次执行”的意思到底是什么。

    1 、如果顺序固定,那么前面请求的意义是什么?
    2 、如果顺序不固定,那么到底哪个请求才是“N5”?是花费时间最长的那个么?
    zhanghx1991
        7
    zhanghx1991  
       113 天前
    我也选 rxjs
    shyling
        8
    shyling  
       113 天前
    没看懂需求。。。

    Promise.all 满足不了你?

    知道要 5 不能只在 5log ?
    ITsWHY
        9
    ITsWHY  
       113 天前
    每个请求设置一个 id 再用一个全局的变量记录最后一个发的 id, if(全局 id != 当前请求 id) 就直接 return
    yxcoder
        10
    yxcoder  
    OP
       113 天前
    @kop1989smurf 顺序执行,没有意义,用户的操作有啥意义可言呢,重要的是解决办法
    ytll21
        11
    ytll21  
       113 天前
    每个请求给一个编号,返回的值压入一个 array 中,按照编号排序,取最大编号的值。

    或者再简单点,只保留一个值,只有当编号大于保留的值的编号时,才做更换。
    Terry05
        12
    Terry05  
       113 天前   ❤️ 1
    感觉你这种类似在短时间里一直轮询的场景,这种情况下如果下一个时间周期,上一个周期的请求还没完成,可以强制 cancel 各种 http request 库都有类似功能
    woodensail
        13
    woodensail  
       113 天前   ❤️ 1
    并发控制有几种不同的处理,可以根据不同需求选用。
    1:发起新请求时如果有进行中的请求,则直接将老请求的 promise 丢回去。这种方式适用于不需要考虑时效性的请求接口。
    2:乐观锁,每次进入长逻辑时将乐观锁+1 然后记录当前的值,逻辑执行中每个异步任务完成后都检查一次乐观锁是否变动,如果没变则可以继续执行,如果变了,则终止当前人物。这个方式适用于包含多个异步人物的长逻辑链条,且允许新操作覆盖旧操作的场景
    3:简单的互斥锁,有进行中的请求则将新的操作废弃或者排队。一般提交类操作这么搞,前一个请求完成前,不允许发起第二个请求
    这几个是我常用的手段,还有其他手段可以参考其他人的回复,比如上面说到的 abort 老的请求。
    jamosLi
        14
    jamosLi  
       113 天前
    建议说场景 不说场景那就干掉异步
    yxcoder
        15
    yxcoder  
    OP
       113 天前
    @mxT52CRuqR6o5 请求没法拦截掉,该走的逻辑还是会走,现在的问题是如何让它知道它后面又有个请求发出去了
    @westoy 好像是可以,用时间戳做标志位,似乎可以解决,我试一下
    @shyling 这里的 5 只是一个例子,可能有很多个,而且这些请求并不是预先就知道的,可能是前一个请求发到一半,来了另一个请求
    @ITsWHY 没法为每一个请求记录一个 ID ,因为请求的数量其实是不固定的
    kop1989smurf
        16
    kop1989smurf  
       113 天前
    @yxcoder #10
    1 、请求触发回调时验证是否有新的请求,如果有则放弃执行回调逻辑。
    2 、执行新请求之前,调用老请求 xhr 的.abort()方法。(各种库封装的不一样,需要针对库来使用)
    yxcoder
        17
    yxcoder  
    OP
       113 天前
    @woodensail 第二种方法应该是 ok 的,和前面一个说时间戳的其实是一个道理
    yxcoder
        18
    yxcoder  
    OP
       113 天前
    @kop1989smurf 你可以看下 2 楼和 13 楼的方案,你说的 1 对应的是互斥锁,2 方法在提问中就已经说明了前者的请求无法取消。可以使用乐观锁或者时间戳解决
    keepeye
        19
    keepeye  
       113 天前
    一个计数器就可以搞定的

    let c = 0
    function req() {
    let n = c++
    // .... 请求过程
    if (n == c) {
    console.log("....")
    }
    }
    yxcoder
        20
    yxcoder  
    OP
       113 天前
    @keepeye 是的,其实就是维护一个无限增长的数
    edward1987
        21
    edward1987  
       113 天前
    // 用闭包变量 res 来承载同一种请求的结果,一旦有新的请求,所有的 req()返回的都指向最新的结果
    let res;
    function req(){
    res = doRequest()
    return res
    }

    async function api(){
    const lastRes = await req()
    }
    del1214
        22
    del1214  
       113 天前
    redux saga takelatest
    edward1987
        23
    edward1987  
       113 天前
    @edward1987 有点问题 修正下
    // 用闭包变量 res 来承载同一种请求的结果,一旦有新的请求,所有的 req()返回的都指向最新的结果
    let res;
    async function req(){
    res = doRequest();
    await res;
    return res;
    }

    async function api(){
    const lastRes = await req()
    }
    dudubaba
        24
    dudubaba  
       113 天前
    封装一个 reduce 就搞定了
    dcsuibian
        25
    dcsuibian  
       113 天前 via Android
    说一下使用场景
    怕的就是 xy 问题
    qzhai
        26
    qzhai  
       113 天前
    @shyling
    @kop1989smurf

    楼主的意思应该是,一个 ajax 的分页 有 5 页,用户依次点击 1 - 5 页的按钮,最终留在某一页,这个时候 ajax 发了 5 此,正常不处理的话,最后一个返回的会 承接当前列表。
    qiayue
        27
    qiayue  
       113 天前
    用一个数组记录每一次的返回结果,再根据前端的状态决定显示哪个结果。
    拿分页这个例子来说的话,如果 5 页数据都返回了,那么之后用户点击任何页面,都不需要再发送请求了,直接从已返回结果里拿数据并显示就好了。
    wunonglin
        28
    wunonglin  
       113 天前
    rxjs 两三行就可以了。搞那么多乱七八糟的
    rrfeng
        29
    rrfeng  
       113 天前
    建议描述下原始需求,console.log 什么的代表不了什么
    ITsWHY
        30
    ITsWHY  
       113 天前
    @yxcoder id 有很多种形式 比如一个自增的数 或者时间戳
    wunonglin
        31
    wunonglin  
       113 天前   ❤️ 1
    Vegetable
        32
    Vegetable  
       113 天前
    看了你的描述场景,我觉得吧

    你直接把展示的价格和展示商品的 ID 做一个映射,显示哪个商品就展示哪个价格,费这么大劲操作请求属于有点把问题搞复杂了
    plusor
        33
    plusor  
       113 天前
    throttle?
    chnwillliu
        34
    chnwillliu  
       113 天前 via Android
    对,rxjs 下 switchMap 很简单。
    dtdths1
        35
    dtdths1  
       113 天前
    最简单的办法就是成功回调时跟最后提交的 id 对比一下,一样再渲染
    chnwillliu
        36
    chnwillliu  
       113 天前 via Android   ❤️ 1
    就是典型的 switchMap 场景,自动 unsubscribe 上一次产生的流,自动切到最新的流上去。

    id$ = new Subject();

    price$ = id$.pipe(
    switchMap(id => getPriceById(id))
    )

    // merge + map 很干净,省一个 subject
    isLoading$ = merge(
    id$.pipe(mapTo(true)),
    price$.pipe(mapTo(false))
    );

    getPriceById 需要返回一个 observable, unsubscribe 时 abort 请求即可。

    切商品直接 id$.next(newId), price$ 和 isLoading$ 会自动更新。上一次没完成的请求 switchMap 会自动 unsubscribe ,简直毫无负担。
    yxcoder
        37
    yxcoder  
    OP
       112 天前
    @chnwillliu 解决办法其实很简单,实在没必要引入 RxJS
    @dtdths1 对的,这也是个解决办法,应该会更好一点
    markgor
        38
    markgor  
       112 天前
    我不是专业前端,
    但是这个需求不是应该是节流和防抖的事吗....
    另外 ajax 请求是可以 abort 的,
    我没理解错的话你意思是 前端快速切换商品,但是由于 ajax 是异步请求,导致最终渲染出来的结果并非最后客户选择的产品结果。
    我觉得这种场景上节流,请求异步改同步就能很好解决了,
    如果为了体验,可以上骨架,请求前开始骨架渲染,结果返回后取消骨架渲染替换真实结果。
    yxcoder
        39
    yxcoder  
    OP
       112 天前
    @markgor
    1.节流需要获取句柄,暂无法获取
    2.受限于框架,无法使用 abort ,问题中已经说过了
    3.JS 是单线程,异步改同步会阻塞 JS 进程
    4.骨架和文案 “价格查询中...” 有什么本质区别吗?
    luvxy
        40
    luvxy  
       112 天前
    promise.all 就行了 等待所有请求完毕 才会给你返回所有结果
    kiritoxf
        41
    kiritoxf  
       112 天前
    看场景感觉像是竞态问题
    jihu777
        42
    jihu777  
       112 天前 via Android
    rxjs ,switchMap
    thulof
        43
    thulof  
       112 天前
    应该是竞态问题。记录最新请求的 id ,如果返回结果携带的 id 不匹配,则忽略
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1572 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 43ms · UTC 18:27 · PVG 02:27 · LAX 10:27 · JFK 13:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.