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

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

  •  
  •   yxcoder · 2022-08-18 16:33:10 +08:00 · 3520 次点击
    这是一个创建于 607 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

    第 1 条附言  ·  2022-08-18 18:02:23 +08:00
    问题已解决,乐观锁,或者使用时间戳即可,可以参看 2 楼和 13 楼的解答


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

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

    1 、如果顺序固定,那么前面请求的意义是什么?
    2 、如果顺序不固定,那么到底哪个请求才是“N5”?是花费时间最长的那个么?
    zhanghx1991
        7
    zhanghx1991  
       2022-08-18 16:42:37 +08:00
    我也选 rxjs
    shyling
        8
    shyling  
       2022-08-18 16:43:55 +08:00
    没看懂需求。。。

    Promise.all 满足不了你?

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

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

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

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

    async function api(){
    const lastRes = await req()
    }
    dudubaba
        24
    dudubaba  
       2022-08-18 17:17:26 +08:00
    封装一个 reduce 就搞定了
    dcsuibian
        25
    dcsuibian  
       2022-08-18 17:21:51 +08:00 via Android
    说一下使用场景
    怕的就是 xy 问题
    qzhai
        26
    qzhai  
       2022-08-18 17:28:58 +08:00
    @shyling
    @kop1989smurf

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

    你直接把展示的价格和展示商品的 ID 做一个映射,显示哪个商品就展示哪个价格,费这么大劲操作请求属于有点把问题搞复杂了
    plusor
        33
    plusor  
       2022-08-18 18:32:17 +08:00
    throttle?
    chnwillliu
        34
    chnwillliu  
       2022-08-18 18:38:00 +08:00 via Android
    对,rxjs 下 switchMap 很简单。
    dtdths1
        35
    dtdths1  
       2022-08-18 19:19:30 +08:00
    最简单的办法就是成功回调时跟最后提交的 id 对比一下,一样再渲染
    chnwillliu
        36
    chnwillliu  
       2022-08-18 19:24:18 +08:00 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
       2022-08-19 10:29:19 +08:00
    @chnwillliu 解决办法其实很简单,实在没必要引入 RxJS
    @dtdths1 对的,这也是个解决办法,应该会更好一点
    markgor
        38
    markgor  
       2022-08-19 10:36:40 +08:00
    我不是专业前端,
    但是这个需求不是应该是节流和防抖的事吗....
    另外 ajax 请求是可以 abort 的,
    我没理解错的话你意思是 前端快速切换商品,但是由于 ajax 是异步请求,导致最终渲染出来的结果并非最后客户选择的产品结果。
    我觉得这种场景上节流,请求异步改同步就能很好解决了,
    如果为了体验,可以上骨架,请求前开始骨架渲染,结果返回后取消骨架渲染替换真实结果。
    yxcoder
        39
    yxcoder  
    OP
       2022-08-19 10:48:26 +08:00
    @markgor
    1.节流需要获取句柄,暂无法获取
    2.受限于框架,无法使用 abort ,问题中已经说过了
    3.JS 是单线程,异步改同步会阻塞 JS 进程
    4.骨架和文案 “价格查询中...” 有什么本质区别吗?
    luvxy
        40
    luvxy  
       2022-08-19 10:54:01 +08:00
    promise.all 就行了 等待所有请求完毕 才会给你返回所有结果
    kiritoxf
        41
    kiritoxf  
       2022-08-19 11:17:01 +08:00
    看场景感觉像是竞态问题
    jihu777
        42
    jihu777  
       2022-08-19 13:02:32 +08:00 via Android
    rxjs ,switchMap
    thulof
        43
    thulof  
       2022-08-19 14:37:03 +08:00
    应该是竞态问题。记录最新请求的 id ,如果返回结果携带的 id 不匹配,则忽略
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3268 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 37ms · UTC 13:17 · PVG 21:17 · LAX 06:17 · JFK 09:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.