Javascript 代码执行先后顺序问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a Javascript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
Javascript 权威指南第 5 版
Closure: The Definitive Guide
sudoy
V2EX    Javascript

Javascript 代码执行先后顺序问题

  •  1
     
  •   sudoy 2020-05-27 13:31:56 +08:00 3582 次点击
    这是一个创建于 2039 天前的主题,其中的信息可能已经有所发展或是发生改变。

    hello 大家好,

    我之前一直用 python 写一些小工具,最近开始用 JS 写东西,发现各种不适应:要么忘记放 ; , 要么数不清 {} 是否成对。 这些都还好,多写一写也就习惯了,现在碰到一个代码执行顺序的逻辑问题:我有一个组订单号码,每个订单号码都要拿去进行 GET 请求,请求结果有一个变量要么 true 要么 false,我需要将根据这个变量将原始的订单号码分两组。

    假设订单号码列表为:ordersID = [11, 12, 13, 21]

    如果是 python,我可以这样写:

    ordersID = [11, 12, 13, 21]; successful = list(); fail = list(); for x in ordersID: if (...): successful.append(x) else: fail.append(x) print(successful, fail) # [11,12, 13] [21] 

    为了精简我把条件部分省掉了

    Javascript 我是这样写的:

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" cOntent="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>Testing!</h1> <script> var ordersID = ['11', '12', '13', '21']; var successful = []; var fail = []; function makeRequest(arg) { fetch(`https://jsonplaceholder.typicode.com/posts/${arg}`, {method: 'GET'}) .then(res => res.json()) .then(function (res) { if (res.userId == 2) { console.log(res); successful.push(arg); fetch('https://jsonplaceholder.typicode.com/posts', { method: 'POST', body: JSON.stringify({ title: 'Some title', body: arg, userId: 2 }), headers: { 'Content-type': 'application/json; charset=' } }) .then(res => res.json()) .then(console.log) } else { console.log('userId != 2'); fail.push(arg) } }); }; for (i = 0; i < ordersID.length; i++) {makeRequest(ordersID[i]); console.log(successful, fail)}; </script> </body> </html> 

    我期望的结果是返回一个successful array 和一个 fail array,分别包含成功和失败的订单号码。可结果是返回空的 array 。我 JS 还没有好好学,只是边做边查,哪位盘友指点一下 :)

    21 条回复    2020-05-28 12:28:40 +08:00
    lonelinsky
        1
    lonelinsky  
       2020-05-27 13:38:21 +08:00
    (你要不先研究下 Python3 的 asyncio,等研究完回来再看,可能就知道问题所在了 :-)
    jybox
        2
    jybox  
       2020-05-27 13:39:16 +08:00
    Promise.all 或 Promise.allSettled
    Marven
        3
    Marven  
       2020-05-27 13:43:15 +08:00
    用 async await
    rabbbit
        4
    rabbbit  
       2020-05-27 13:44:26 +08:00   1
    fetch(`https://jsonplaceholder.typicode.com/posts/${arg}`, {method: 'GET'})
    ->
    return fetch(`https://jsonplaceholder.typicode.com/posts/${arg}`, { method: 'GET' })

    for (i = 0; i < ordersID.length; i++) {makeRequest(ordersID[i]); console.log(successful, fail)};
    ->

    for (i = 0; i < ordersID.length; i++) {
      makeRequest(ordersID[i]).then(() => {
       console.log(successful, fail);
     });
    };


    相关知识点
    Promise
    event loop
    macrotask 和 microtask
    sudoy
        5
    sudoy  
    OP
       2020-05-27 13:49:31 +08:00
    @rabbbit @Marven @jybox @lonelinsky 哇!你们太给力,给你们点赞
    azcvcza
        6
    azcvcza  
       2020-05-27 14:50:42 +08:00
    你的 console.log 最好都丢在和 then 同一个括号里; 首先 JS 自顶向下执行代码, 如果碰到 setTimeout,或者 promise 就把他塞到事件队列尾,下一个周期再执行;你这里 console.log,必然拿不到请求的数据
    CyHstyle
        7
    CyHstyle  
       2020-05-27 14:59:51 +08:00
    浏览器执行 js 是有顺序的,自顶向下执行所有的同步 js,然后再执行异步回调。所以你先用一个 for 循环去调用 fetch,然后就 console 了结果,那么此时 fetch 还没有请求回来,是一个异步的,所以 console 打印出来的就是空的,要在 fetch 的回调函数里打印,或者,用 async await 阻塞异步请求。
    sayitagain
        8
    sayitagain  
       2020-05-27 15:01:23 +08:00
    js 请求并不会等到请求结束才往下走,有可能这次请求发出去还没收到响应值就已经执行 console.log(successful, fail)了...要么把 console.log(successful, fail)放在 then 里,要么把请求设置为同步...
    whatCanIDoForYou
        9
    whatCanIDoForYou  
       2020-05-27 15:31:36 +08:00
    想学习 你怎么粘贴代码的。。。。
    JayLin1011
        10
    JayLin1011  
       2020-05-27 15:49:28 +08:00
    `Promise.allSettled()` 能满足你的需求啊,请求结果自带 `status`,根据它判断成功失败就好,或者直接根据有没有 `value` 或 `reason` 判断也行。你这种属于串行的继发请求模式,`async..await` 语法会更适合这个场景,写起来时序更直观,不然像这样容易就把 `Promise` 优雅的链式调用活生生写成「回调套娃」。温馨提示:建议 `fetch()` 可以封装一下。
    https://developer.mozilla.org/zh-CN/docs/Web/Javascript/Reference/Operators/await
    Shy07
        11
    Shy07  
       2020-05-27 16:15:56 +08:00
    JS 一般推荐函数式的写法:

    const promises = orderID.map(makeRequest)
    const result = await Promise.all(promises)
    const successfull = result.filter(val => res.status === 'ok')
    const fail = result.filter(val => res.status !== 'ok')
    JayLin1011
        12
    JayLin1011  
       2020-05-27 16:27:53 +08:00
    @Shy07 `status` 的值要么 `fulfilled`,要么 `rejected`。。
    Shy07
        13
    Shy07  
       2020-05-27 16:39:00 +08:00
    @JayLin1011 个人比较倾向在 makeRequest 里预先处理一下请求结果,比如返回 { status: string, arg: any, err: Error }
    sudoy
        14
    sudoy  
    OP
       2020-05-27 16:49:46 +08:00
    还是 V 友给力,这么多回答!多谢各位啦!

    @whatCanIDoForYou
    > 想学习 你怎么粘贴代码的。。。。

    V 站发帖的时候支持 markdown 语法的, 但是回复帖子的时候似乎不支持
    JayLin1011
        15
    JayLin1011  
       2020-05-27 17:10:57 +08:00
    @Shy07 。。不是请求处理的问题,`Promise.all()` 的返回值格式是固定的。
    jones2000
        16
    jones2000  
       2020-05-27 18:57:22 +08:00
    直接 js 裸写一个下载队列,批量下载不就可以了。如果迁移到其他平台,换成多线程下载就可以了。

    ```Javascript
    function OnFinished(aryResult)
    {
    console.log("下载完成了", aryResult)
    }

    var download=new JSDownload();
    download.FinishCallback=OnFinished;
    var downloadList=
    [
    {Url:"https://abc"},
    {Url:"https://abc1"},
    {Url:"https://abc2"}
    ];
    download.SetDownload(downloadList);
    download.Start();
    ```

    ```Javascript
    //批量下载
    function JSDownload()
    {
    this.DownloadData;
    /*
    Key:url
    Value:
    {
    Status: 状态 0=空闲 1=下载中, 20=下载成功 30=失败 ,
    Message:错误信息,
    ProcSuccess: 单个数据到达回调(不处理就 null)
    ProcFailed: 单个数据失败回调(不处理就 null)
    RecvData: 接收到的数据
    }
    */
    this.FinishCallback; //全部下载完回调

    //设置批量下载地址
    this.SetDownload=function(aryDownload)
    {
    this.DownloadData=new Map();
    for(var i in aryDownload)
    {
    var item=aryDownload[i];
    if (this.DownloadData.has(item.Url)) continue;

    var downItem={Url:item.Url, ProcSuccess:item.ProcSuccess, ProcFailed:item.ProcFailed , Status:0 };
    this.DownloadData.set(downItem.Url,downItem);
    }
    }

    //是否全部下载完成
    this.OnFinished=function()
    {
    for (var item of this.DownloadData) //遍历下载队列 是否都下载完了
    {
    if (item[1].Status==20 || item[1].Status==30) continue;

    return;
    }

    if (this.FinishCallback) //通知回调 数据都下载完了
    {
    var aryResult=[];
    for (var item of this.DownloadData)
    {
    var downloadItem=item[1];
    if (downloadItem.Status==20)
    aryResult.push({Url:downloadItem.Url, Success:true, Data:downloadItem.RecvData});
    else
    aryResult.push({Url:downloadItem.Url,Success:false});
    }

    this.FinishCallback(aryResult);
    }
    }

    //开始下载
    this.Start=function()
    {
    var self=this;
    for (var item of this.DownloadData)
    {
    console.log('[JSDownload::Start] start dowloand ', item[0]);
    this.AjaxDownload(item[1]);

    }
    }

    this.AjaxDownload=function(item)
    {
    var self=this;
    $.ajax({
    url: item.Url,
    type:"get",
    dataType: "json",
    async:true,
    success: function (data)
    {
    if (item.ProcSuccess) item.ProcSuccess(data, self);
    item.RecvData=data;
    item.Status = 20;
    self.OnFinished();
    },
    error:function(jqXHR, textStatus, errorThrown)
    {
    if (item.ProcFailed) item.ProcFailed(jqXHR,self);
    item.Status = 30;
    self.OnFinished();
    }
    });
    item.Status=1;
    }
    }
    ```
    JayLin1011
        18
    JayLin1011  
       2020-05-27 21:46:37 +08:00
    @Shy07 对。但我的 `promises` 不会全部成功,我还是选择 `Promise.allSettled()`。
    oukichi
        19
    oukichi  
       2020-05-27 21:54:05 +08:00
    为啥要 then 里面套 then ?
    linZ
        20
    linZ  
       2020-05-28 11:13:44 +08:00
    可以 resolve(promise)的,然后试一下 async await 呀,看下谁给你写个 async await 版本的,你就明白啦
    pomelotea2009
        21
    pomelotea2009  
       2020-05-28 12:28:40 +08:00 via Android
    搜:js async all
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1036 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 18:23 PVG 02:23 LAX 10:23 JFK 13:23
    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