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 · 3175 次点击
    这是一个创建于 1674 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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=UTF-8'
                            }
                        })
                        .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   ·   实用小工具   ·   1113 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 23:04 · PVG 07:04 · LAX 15:04 · JFK 18:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.