逐步加深的异步操作(1)

作者: KlivitamJ | 来源:发表于2018-07-13 23:59 被阅读12次

         最近呢?第一主要是加班比较严重,还被高中同桌嘲讽:屌丝程序员,还讽刺我头发掉的快等等;第二呢 我最近和大学室友准备开一个开源项目,最近正在疯狂的筹划中,你能想象 设计:自己人、后台:自己人、前端:自己人、ui没有的痛苦吗?但是我们还是想做一个出来。哈哈,反正我们这种屌丝的想法不要钱。😂😂😂
       好了,不哔哔了,这篇文章主要是来研究异步操作的。如果做过Android端的都知道,Android端有rxjava、多线程、aysnctask等好多东西来处理同步和异步的问题,但是初涉前端,很多东西都是朦胧的,我就由浅及深谈一谈我所理解的异步操作。

    说在前头:

    同步和异步呢?是程序员难以理解的一个东西,其实我对同步、异步也是略窥门径。我就简单说说我了解的同步、异步。
    说起同步、我在这里把同步异步来做一个小例子帮助大家理解:
    背景:小明暗恋着小红
    同步:就好比:小明约小红去吃饭,小明必须得到小红的响应才能做下一步动作,不然小明会在处于等待状态。
    异步:就好比:小红约小明去吃饭。小红对着空气喊了一声“小明,我去吃饭了”,然后小红就扬长而去了,不需要得到小明的回应。
    总的来说,同步异步就只需要知道四个概念:

    • 同步
      所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。=
    • 异步
      异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。=
    • 阻塞
      阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
    • 非阻塞
      非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

    一、黄阶下级斗技:胡搞一通,能运行就好了

    我们刚开始入门的时候,经常会用一种完成任务的心态去实现功能。就例如现在页面启动的时候有N个请求,这就很有可能会写成这个样子:

    {
        function get1(){
            console.log("get1...")
            setTimeout(function(){
                post1()
            },2000)
        }
        function get2(){
            console.log("get2...")
            setTimeout(function(){
                post2()
            },2000)
        }
        function post1(){
            console.log("post1...")
            setTimeout(function(){
                get2()
            },2000)
        }
        function post2(){
            console.log("post2...")
            setTimeout(function(){
                console.log("render...")
            },2000)
        }
        get1()
    }
    

    运行结果如下


    处理结果

    当然,还有更恐怖堆积在一个函数里面。这样的写法的好处就是所有的东西都处于一根线上面,便于大家理解。但是我们设身处地的想一下:如果你是用户,打开网站到看到视图需要这么长的白屏时间,这种体验就大打折扣。此时就需要异步操作了。
    看下面一段代码

    {
        function get1(){
            console.log("get1...")
            setTimeout(function(){
                console.log("get1 end...")
            },1000)
        }
        function get2(){
            console.log("get2...")
            setTimeout(function(){
                console.log("get2 end...")
            },1000)
        }
        function post1(){
            console.log("post1...")
            setTimeout(function(){
                console.log("post1 end...")
            },1000)
        }
        function post2(){
            console.log("post2...")
            setTimeout(function(){
                console.log("post2 end and render...")
            },1000)
        }
        get1()
        get2()
        post1()
        post2()
    }
    

    运行结果如下:


    运行结果

    如果按照我们通常的想法应该是get1 end之后才开始get2 end。但是上面的结果却大相径庭。查阅的一些官方文档,我也差不多略窥门径了,首先在js中存在一个执行队列和一个异步队列,如果存在有耗时任务,就会将该任务直接丢到异步队列,然后继续运行执行队形队列的任务。这就是js自己实现的一个异步操作。

    那既然js内部自己就已经开始在使用异步了,我们为什么不好好来研究一波异步操作呢?

    二、 黄阶上级斗技:回调

    如果你和我一样,都有一点java/android的底子,就一定会对回调有某种执念,同样js内部也可以完成这种写法,具体代码如下:

    {
        function get1(...callback){
            console.log("get1...")
            setTimeout(function(){
                console.log("get1 end...")
                for(let index of callback){
                    index()
                }
            },1000)
            console.log("do something...")
        }
        function get2(){
            console.log("get2...")
            setTimeout(function(){
                console.log("get2 end...")
            },1000)
        }
        function post1(){
            console.log("post1...")
            setTimeout(function(){
                console.log("post1 end...")
            },1000)
        }
        function post2(){
            console.log("post2...")
            setTimeout(function(){
                console.log("post2 end and render...")
            },1000)
        }
        get1(get2,post1,post2)
    }
    

    运行结果如下:

    运行结果
    这种写法看了大概就能明白回调的用法了。但是回调是什么?
    百科是这样解释的:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
    在js中,我看到一种解释是这样说的:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

    当然回调不一定只用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。

    {
        function f1(callback){
            console.log("do something=======>");
            // do somethings
            (callback && typeof(callback) === "function")&& callback();
        }
    
        function f2(){
            console.log("do others things======>");
        }
        f1(f2)
    }
    

    关于回调呢?就算这么多,具体的思想前面也说了,es5以前回调是处理异步的主要方式。当然现在都2018年,es也出了很多的新方法,我来首先介绍一个promise。

    三、 玄阶中级斗技:promise

    说起promise这个东西,其实我在刚开始接触到前端的时候就有用到,主要是用来异步操作网络请求。首先介绍的是promise的两个特点:

    (1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

    (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。

    pomise内存实现了两个方法:resolve、reject。resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

    {
        //模拟一个网络请求
        let promise = new Promise((res,rej)=>{
            console.log("promise start====>")
            let data = Math.random()*10
    
            setTimeout(()=>{
                if(data>2){
                    res("promise end sussess!!")
                }else{
                    rej("promise end fail!!")
                }
            
            },3000)
        })
        promise.then(res=>{
            console.log(res)
        },res=>{
            console.log(res)
        })
    }
    

    效果图如下:

    模拟网络请求成功
    模拟网络请求失败
    由上面的效果我们可以明明白白的看出来promise新建之后就会立即执行。好的,我们这里开始解释前面的说法。promise在new出来之后就开始执行,里面会收到两个回调--resolvereject。我们这里把知道触发这两个回调的状态叫做pending,一旦触发两个回调之后就会立刻就会凝固。
    {
        //模拟一个网络请求
        let promise = new Promise((res,rej)=>{
            console.log("promise start====>");
            throw new Error("test error")
        })
        promise.then(res=>{
            console.log(res)
        },res=>{
            console.log(res.message)
        })
    }
    
    结果
    现在我们在模拟网络请求中出现错误的情况,如上可知reject本身就是可以拦截错误的。如果在promise中出错之后会走reject回调。但是我在看很多人的源码,很多大佬是这么写的:
    {
        let promise = new Promise((res,rej)=>{
            console.log("promise start====>");
            throw new Error("test error")
            res("promise end sussess!!")
        })
        promise.then(res=>{
            console.log(res)
        }).catch(res=>{
            console.log(res.message)
        })
    }
    

    刚开始我没有想通,但是当我写了很多东西之后就开始逐渐明白了。如下面的例子

    {
        let promise = new Promise((res,rej)=>{
            console.log("promise start====>");
            res("promise end sussess!!")
        })
        promise.then(res=>{
            console.log(res)
            throw new Error("i meet some bug = =.");
        },res=>{
            console.log(res)
        })
    }
    

    效果图如下:

    效果图
    我们发现reject并不能捕获then里面的错误,此时就用到大神们推荐的方式了。
    {
        //模拟一个网络请求
        let promise = new Promise((res,rej)=>{
            console.log("promise start====>");
            res("promise end sussess!!")
        })
        promise.then(res=>{
            console.log(res)
            throw new Error("i meet some bug = =.");
        }).catch(res=>{
            console.log(res.message)
        })
    }
    

    效果图如下:

    效果图
    并且我了解到源码中看到catch的实现方式就是:
    then(null, rejection)    <===> catch
    

    我从进一步了解源码到:无论是resolve,和是reject都会返回一个promise,所以现在的代码就可以这么来写。

    {
    {
       let promise = new Promise((res,rej)=>{
           console.log("promise start====>");
           res("promise end sussess!!")
       })
       promise.then(res=>{
           console.log(res)
       }).then(res=>{
           console.log(res)
           return "next next promise!!"
       }).then(res=>{
           console.log(res);
           throw new Error("i meet some bug = =.");
       }).catch(res=>{
           console.log(res.message)
       }).then(res=>{
           throw new Error("ai ya = =.");
       })
    }
    

    效果图如下:


    image.png

    这里值得注意的两点:

    • 如果没有return下面的then是会返回undefined,相当于系统默认reject(undefined)
    • catch 并不能捕获到之后的error,如果想捕捉,就得继续去捕获了。

    说到这里了,我就再插一句:我在最新的es2018中看到了finally方法,类比于java中的try,catch, finally,如果有兴趣,可以去看看最新的es2018的规则。我在这里就不做赘述了。我在这里最后来介绍promise在我的开发途径中常用的几个方法:

    • Promise.all()
      使用方法:
    {
        let p1 = new Promise((res,rej)=>{
            res('success!!');
        });
    
        let p2 = Promise.resolve("dosomething");
    
    
        Promise.all([p2,p1]).then(res=>{
            console.log(res);
        }).catch(res=>{
            console.log(res)
        })
    
    }
    

    效果图:

    效果图
    上面值得注意的是:执行顺序是按照all方面里面的数组顺序执行的,这就能为我们封装网络请求做一定的便利。
    {
        let p1 = new Promise((res,rej)=>{
            res('success!!');
        });
    
        let p2 = Promise.resolve("dosomething");
    
        let p3 = Promise.reject("i have bug ...")
    
        Promise.all([p1,p2,p3]).then(res=>{
            console.log(res);
        }).catch(res=>{
            console.log(res)
        })
    
    }
    

    如果all之中存在抛出错误,其效果图如下

    效果图
    如果存在错误直接走catch方法.
    我在这里做一下总结:
    ** Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。**
    • Promise.race()
      使用方法:
    {
        let p1 = new Promise((res,rej)=>{
            res('success!!');
        });
    
        let p2 = Promise.resolve("dosomething");
    
        let p3 = Promise.reject("i have bug ...")
    
    
        Promise.race([p1,p2,p3]).then(res=>{
            console.log(res);
        }).catch(res=>{
            console.log(res)
        })
    
    }
    

    效果图如下:

    效果图
    race相较于all方法就是,里面promise谁先处于完成状态就会走哪个方法。

    说在最后

    计划
    我看了时间点又看了看我的计划,看来我的焚决今晚是写不完了。先就这样吧。因为考虑到Generator这个到使用都花了近三天的时间,我来讲这个不知道一个小节能不能讲的通,更何况还有decorator这种bug呢?。

    写文章第一:是为了梳理自己的知识体系;第二:写给读者。如果我写的东西,让一个新人都能看懂,这就说明我也达到了梳理知识体系的效果,所以我决定将异步操作分成几块来讲解,争取能让所有看文章的人都明白我写的东西。

    12点了,不写了 不写了,好了,洗澡去了...最后说一句🤣🤣🤣 小米涨了 ahhhhhhhhhh

    相关文章

      网友评论

        本文标题:逐步加深的异步操作(1)

        本文链接:https://www.haomeiwen.com/subject/iosfeftx.html