JavaScript之——异步编程

10 篇文章 1 订阅

异步编程

异步是什么,有哪些操作是异步的,有什么用,怎么用

  • 同步是什么,异步又是什么

    同步是指同一时间只能做一件事,也就是说一件事情做完了才能做另外一件事

    异步是指多件事可以在同一时间执行

  • 异步的操作

    1. ajax请求

    2. 定时器setTimeout、setInterval

    3. JS的异步加载(例如外部文件的异步加载,只兼容IE的defer(也可以异步加载script标签内部的js代码)、async(不能将js代码写在有async属性的script标签内),支持chrome,safari,firefox,opera浏览器、动态创建script标签(封装代码如下) )

     function loadAsyncScript(url, callback) {
         var script = document.createElement('script');
          script.type = 'text/javascript';
          if (script.readyState) {
              // 兼容IE浏览器
              // 脚本加载完成事件
              script.onreadystatechange = function () {
                  if (script.readyState === 'complete' || script.readyState === 'loaded') {
                      callback();
                  }
              }
          } else {
              // Chrome, Safari, FireFox, Opera可执行
              // 脚本加载完成事件
              script.onload = function () {
                 callback();
              }
          }
         script.src = url; //将src属性放在后面,保证监听函数能够起作用
          document.head.appendChild(script);
      }
      //封装的方法loadAsyncScript(url, callback)传入两个参数,第一个js文件的地址,第二个是回掉函数,让的用户可以再里面执行异步加载文件里面的方法或者对象。
    
    1. DOM事件、IO
  • 异步有什么用

    javascript是单线程的,优点是操作简单,执行环境单纯,但是缺点很明显,如果某一个任务耗时很长,后面的任务就很会一直等待,可能伴随出现假死现象。所以可以在需要等待但又不能阻塞(就是 一个时间只能做一个事情,上一件事情没做完,不能做下一件事情)程序的时候使用异步,解决后面任务一直排队等待的问题,提升效率

    主线程的任务以同步的方式执行完毕,才会去依次执行任务列队中的异步任务

  • 异步编程的四种方法

    1. 回调函数
    //有两个函数f1和f2,后者等待前者的执行结果。
      f1();
      f2();
    //如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。
      function f1(callback){
        setTimeout(function () {
          // f1的任务代码
          callback();
        }, 1000);
      }
    //执行代码就变成下面这样:
      f1(f2);
    //采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
    //多层嵌套容易产生回调地狱。
    
    
    //回调地狱
    //比如,我想打印4句话,但是每句话都在前一句话的基础上延迟2秒输出。代码如下:
    setTimeout(function(){
        console.log("first");
        setTimeout(function(){
            console.log("second");
            setTimeout(function(){
                console.log("third");
                setTimeout(function(){
                    console.log("fourth");
                },2000);
            },2000);
        },2000);
    },2000);
    //这就产生了回调地狱的问题,内容越多,嵌套的层次越多
    
    
    1. 事件监听
     //实现事件监听
     /*
       DOM 可以被监听事件的对象
       .on("eventName",function)         第一个参数为监听的事件名,第二个参数为  绑定  的方法
       .removeOn("eventName",function)   第一个参数为监听的事件名,第二个参数为  解绑  的方法
       .trigger("eventName")             参数接收事件名,触发该事件(并触发该事件绑定的所有方法)
     */
     //以下是用事件监听解决异步的方案
     
     //生成两个DOM实例
     let dom1 = new DOM();
     let dom2 = new DOM();
    //异步模拟
     let f2 = function () {
       setTimeout(function () {
         console.log("开始触发事件:");
         console.log("dom1 'done' 事件 触发:");
         dom1.trigger('done', 20);//触发f3
         console.log("dom2 'done' 事件 触发:");
         dom2.trigger("done", "123");//触发f3
       }, 100);
     };
     
     let f3 = function (data) {
       console.log("f3 run",data)
     };
     
     //为dom1,dom2的'done' 事件绑定 f3 方法
     console.log("dom1 On f3");
     dom1.on("done", f3);
     console.log("dom2 On f3");
     dom2.on("done", f3);
     f2();
     
     setTimeout(function () {
       console.log("dom1 removeOn f3");
       dom1.removeOn("done", f3);
       f2();
     }, 200);
     //事件监听模式不用关心被绑定的函数什么时候执行,我们只需要在特定的时候触发对应的事件就可以了。同时这个模式也相当的依赖事件,变成事件驱动,运行流程会变得很不清晰。
    
    1. Promise
    //ES6中引入了Promise,Promise是异步的一种解决方案
    //Promise简单来说就是一个容器,里面保存着未来才会结束的的事件(一般是异步的)的结果
    
    //promise有以下两个特点:
    1、对象的状态只有异步操作的结果决定,其他外界任何操作无法影响promise对象的状态(pending、resolve、reject)
    2、一旦状态改变就不会再变,任何时候得到的都是这个结果
    
    //首先需要创建一个Promise对象,该对象的构造函数中接收一个回调函数,回调函数中可以接收两个参数,resolve和reject。注意,这个回调函数是在Promise创建后就会调用。它实际上就是异步操作的第一步。那第二步操作再在哪里做呢?Promise把两个步骤分开了,第二步通过Promise对象的then方法实现。
    
    let pm = new Promise(function(resolve,reject){
       //dosomething
    });
    console.log("go on");
    pm.then(function(){
        console.log("异步完成");
    });
    //不过要注意的是,then方法的回调函数不是说只要then方法一调用它就会调用,而是在Promise的回调函数中通过调用resolve触发的。
    
    let pm = new Promise(function(resolve,reject){
        resolve();
    });
    console.log("go on");
    pm.then(function(){
        console.log("异步完成");
    });
    //实际上Promise实现异步的原理和之前纯用回调函数的原理是一样的。只是Promise的做法是显示的将两个步骤分开来写。then方法的回调函数同样会先放入队列中,等待所有的同步方法执行完后,同时Promise中的resolve也被调用后,该回调函数才会执行。
    
    //调用resolve时还可以把数据传递给then的回调函数。
    
    let pm = new Promise(function(resolve,reject){
        resolve("data data data");
    });
    console.log("go on");
    pm.then(function(data){
        console.log("异步完成",data);
    });
    //reject是出现错误时调用的方法。它触发的不是then中的回调函数,而是catch中的回调函数。比如:
    
    let err = false;
    let pm = new Promise(function(resolve,reject){
        if(!err){
            resolve("this is data");
        }else{
            reject("fail");
        }
    
    });
    console.log("go on");
    pm.then(function(data){
        console.log("异步完成",data);
    });
    pm.catch(function(err){
        console.log("出现错误",err);
    });
    //下面,我把刚才时间函数的异步操作用Promise实现一次。当然,其中setTimeout还是需要使用,只是在它外面包裹一个Promise对象。
    
    let pm = new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve();
        },2000);
    
    });
    console.log("go on");
    pm.then(function(){
        console.log("异步完成");
    });
    //接下来做做同步效果。
    
    let timeout = function(time){
        return new Promise(function(resolve,reject){
            setTimeout(function(){
                resolve();
           },time);
        });
    }
    console.log("go on");
    timeout(2000).then(function(){
        console.log("first");
        return timeout(2000);
    }).then(function(){
        console.log("second");
        return timeout(2000);
    }).then(function(){
        console.log("third");
        return timeout(2000);
    }).then(function(){
        console.log("fourth");
        return timeout(2000);
    });
    //由于需要多次创建Promise对象,所以用了timeout函数将它封装起来,每次调用它都会返回一个新的Promise对象。当then方法调用后,其内部的回调函数默认会将当前的Promise对象返回。当然也可以手动返回一个新的Promise对象。我们这里就手动返回了一个新的计时对象,因为需要重新开始计时。后面继续用then方法来触发异步完成的回调函数。这样就可以做到同步的效果,从而避免了过多的回调嵌套带来的“回调地狱”问题。
    
    //如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,不用担心是否错过了某个事件或信号
    
    1. async和await
     //在ES7中,加入了async函数来处理异步。它实际上只是生成器的一种语法糖而已,简化了外部执行器的代码,同时利用await替代yield,async替代生成器的(*)号。下面还是来看个例子:
     
     async function delay(){
         await new Promise((resolve) => {setTimeout(()=>{resolve()},2000)});
         console.log("go on);
     }
     delay();
     //把生成器的(*)号被换成了async。async关键字必须写在function的前面。如果是箭头函数,则写在参数的前面:
     
     const delay = async () => {}
     //在函数中,第一句用了await。它替代了yield。后面同样需要跟上一个Promise对象。接下来的打印语句会在上面的异步操作完成后执行。外部调用时就和正常的函数调用一样,但它的实现原理和生成器是类似的。因为有了async关键字,所以它的外部一定会有相应的执行器来执行它,并在异步操作完成后执行回调函数。只不过这一切都被隐藏起来了,由JS引擎帮助我们完成。我们需要做的就是加上关键字,在函数中使用await来执行异步操作。这样,可以大大的简化异步操作。同时,能够像同步方法一样去处理它们。
     
     //接下来我们再来看看更细节的一些问题。await后面必须是一个Promise对象,这个很好理解。因为该Promise对象会返回给外部的执行器,并在异步动作完成后执行resolve,这样外部就可以通过回调函数处理它,并将结果传递给生成器。
     
     //如果await后面跟的不是Promise对象
     const delay = async () => {
         let data = await "hello";
         console.log(data);
     }
     //这样的代码是允许的,不过await会自动将hello字符串包装一个Promise对象。就像这样:
     
     let data = await new Promise((resolve,reject) => resolve("hello"));
     //创建了Promise对象后,立即执行resolve,并将字符串hello传递给外部的执行器。外部执行器的回调函数再将这个hello传递回来,并赋值给data变量。所以,执行该代码后,马上就会输出字符串hello。虽然代码能够这样写,但是await在这里的意义并不大,所以await还是应该用来处理异步方法,同时该异步方法应该使用Promise对象。
         
     //async函数里面有返回值
     const delay = async () => {
         await new Promise((resolve) => {setTimeout(()=>{resolve()},2000)});
         return "finish";
     }
     let result = delay();
     console.log(result);
     //在delay函数中先执行等待2秒的异步操作,然后返回字符串finish。外部调用时我用一个变量接收它的返回值。最后输出的结果是:
    
     // 没有任何等待立即输出
     Promise { <pending> }
     // 2秒后程序结束
     //我们可以看到,没有任何等待立即输出了一个Promise对象。而整个程序是在2秒钟后才结束的。由此看出,获取async函数的返回结果实际上是return出来的一个Promise对象。假如return后面跟着的本来就是一个Promise对象,那么它会直接返回。但如果不是,则会像await一样包裹一个Promise对象返回。所以,想要得到返回的具体内容应该这样:
     
     const delay = async () => {
         await new Promise((resolve) => {setTimeout(()=>{resolve()},2000)});
         return "finish";
     }
     let result = delay();
     console.log(result);
     result.then(function(data){
         console.log("data:",data);
     });
     //执行的结果:
     
    // 没有任何等待立即输出
     Promise { <pending> }
     //等待2秒后输出
     data: finish
     //那如果函数没有任何返回值,得到的又是什么呢?我将上面代码中取掉return,再次运行:
     
     // 没有任何等待立即输出
     Promise { <pending> }
     //等待2秒后输出
     data: undefined
     //可以看到,仍然可以得到Promise对象,但由于函数没有返回值,所以就不会有任何数据传递出来,那么打印的结果就是undefined。
     
     //用JQuery的AJAX方法实现。
    async function getUsers(){
         let response = await fetch("/users");
         let data = await response.json();
         console.log("data",data);  
     }
     getUsers();
     //这是fetch方法的实现。
     //从这两个例子可以看出,async和生成器两种方式都很类似,但async可以不借助任何的第三方模块,也更易于理解,async表示该函数要做异步处理。await表示后面的代码是一个异步操作,等待该异步操作完成后再执行后面的动作。如果异步操作有返回的数据,则在左边用一个变量来接收它。
     //await可以让异步操作变为同步的效果,那需要让多个异步操作同时进行怎么办呢?方法就是执行异步方法时不加await,这样它们就可以同时进行,然后在获取结果时用await。比如:
     
     function time(ms){
         return new Promise((resolve,reject) => {
             setTimeout(()=>{resolve()},ms);
         });
     }
     const delay = async () => {
         let t1 = time(2000);
         let t2 = time(2000);
         await t1;
         console.log("t1 finish");
         await t2;
         console.log("t2 finish");
     }
     delay();
     //我先把时间函数的异步操作封装成了函数,并返回Promise对象。在delay函数中调用了两次time方法,但没有用await。也就是说这两个时间函数的执行是“同时”(其实还是有先后顺序)进行的。然后将它们的Promise对象分别用t1和t2表示。先用await t1。表示等待t1的异步处理完成,然后输出t1 finish。接着再用await t2,等待t2的异步处理完成,最后输出t2 finish。由于这两个时间函数是同时执行,而且它们的等待时间也是一样的。所以,当2秒过后,它们都会执行相应的回调函数。运行的结果就是:等待2秒后,先输出t1 finish,紧接着立即输出 t2 finish。
     
     const delay = async () => {
         await time(2000);
         console.log("t1 finish");
         await time(2000);;
         console.log("t2 finish");
     }
     //如果是这样写,那么执行的结果会是等待2秒后输出t1 finish。再等待2秒后输出t2 finish。
     //async确实是一个既好用、又简单的异步处理方法。但是它的问题就是不兼容老的浏览器,只有支持了ES7的浏览器才能使用它。
     //最后,还需要注意一个问题:await关键字必须写在async定义的函数中。
    
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

summer·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值