JS 多次请求 如何使后者覆盖前者 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
yxcoder
V2EX    程序员

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

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

    多个 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  
    PRO
       2022-08-18 17:46:03 +08:00
    用一个数组记录每一次的返回结果,再根据前端的状态决定显示哪个结果。
    拿分页这个例子来说的话,如果 5 页数据都返回了,那么之后用户点击任何页面,都不需要再发送请求了,直接从已返回结果里拿数据并显示就好了。
    shakaraka
        28
    shakaraka  
    PRO
       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 有很多种形式 比如一个自增的数 或者时间戳
    shakaraka
        31
    shakaraka  
    PRO
       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     Solana     2657 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 09:47 PVG 17:47 LAX 02:47 JFK 05:47
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86