美文网首页JavaScript程序员Web前端之路
JavaScript学习笔记(5) 异步-- Promise

JavaScript学习笔记(5) 异步-- Promise

作者: 机智的马里奥 | 来源:发表于2017-03-20 02:06 被阅读90次

    写在前面

    异步编程对Javascript语言非常重要,在Javascript的发展道路上,异步编程的方法也是一直在不断更新。关于这方面的知识,网上已经有很多成熟的教程和讲解,我将对这些教程进行整理和归纳,整理出异步JS异步编程的几种解决方法。

    Javascript的异步执行

    Javascript的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和超市等待付款一样,前面的人还没有结账,后面的人就只能等着。这种模式的坏处很明显,就是如果有一个任务耗时很长,就会拖延整个程序的执行。为了解决这个问题,Javascript将任务的执行模式分为两种:同步(synchronous)和异步(Asynchronous)。

    • 同步模式就是前面提到的这种,后一个任务等待前一个任务结束再执行,程序执行的顺序与任务排列的顺序是一致的,同步的。
    • 异步模式,通俗点说,就是前面排队的人告诉后面排队的一个准确时间,这样后面的人就可以利用这段时间去干些别的事情。异步模式可以通过回调函数实现,假设我们需要进行一个耗时的数据请求,在对外部数据发送请求后,程序的执行权将会交给别的任务,一直等到外部数据返回以后,系统才会通知执行回调函数,而回调函数中通常会包括对获取的数据的处理方法。所以,在这个模式下,程序的执行顺序和任务的排列顺序是不一致的,异步的。

    Promise

    Promise是异步编程的一种解决方案,ES6原生提供了Promise对象。

    特点

    • Promise对象有三种状态:Pending(进行中),Resolved(已完成),Rejected(已失败),只有异步操作的结果可以决定当前是哪种状态。
    • Promise的状态改变可能有两种情况:pending->Resolved或者pending->Rejected,一旦发生,状态就会保持不变。

    基本用法

    • Promise对象是一个构造函数,用来生成Promise实例。
    • Promise构造函数接受一个函数作为参数,该函数的两个参数分别为resolve和reject,他们是两个函数,由JS引擎提供,不用自己部署。
    • resolve函数在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
    • reject函数在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
    • then方法可以接受两个回调函数作为参数,第一个回调函数时Promise对象的状态变为resolved时候调用,第二个是Promise对象状态变为reject时调用,第二个函数可选。
    • 如果Promise对象状态变为reject时,会调用catch方法指定的回调函数处理这个错误,所以,除了定义then的第二个参数以外,也可以定义catch方法的回调函数来处理Promise抛出的错误。
    例1 (简单例子)
    function test(resolve,reject){
        var timeout=Math.random()*2;
        console.log('设置timeout为'+timeout+'秒');
        setTimeout(function () {
          if(timeout<1){
            console.log('call resolve()');
            resolve(" resolved");
          }else {
            console.log('call reject()');
            reject(" rejected");
          }
        }, timeout*1000);
    }
    
    //写法1
    var p1=new Promise(test);
    p1.then(function(result){
      console.log("成功"+result);
    },function(result){
      console.log("失败"+result);
    });
    
    //写法2
    var p1=new Promise(test);
    var p2=p1.then(function(result){
      console.log("成功"+result);
    });
    
    var p3=p2.catch(function(result){
      console.log("失败"+result);
    })
    
    /*
    输出:
       设置timeout为1.3983493377635763秒
       call reject()
       失败 rejected
    或者:
       设置timeout为0.8257771710631485秒
       call resolve()
       成功 resolved
      */
    
    

    这个例子中p1是promise实例,方法一和方法二所表达的意思是一样的,只是写法不同,方法1用的就是上面说过的给then传递两个回调函数作为参数的写法。方法二只将then用于函数执行成功的情况,而使用catch来处理函数执行不成功的情况。一般倾向于使用catch方法定义reject状态的回调函数。
    上述的test函数执行成功的情况下,即随机数小于1的情况下,我们将调用resolve(' resolved'),在执行失败的情况下,将调用reject(" rejected")。变量p1是一个Promise对象,负责执行test函数。当test函数执行成功时,then方法指定的回调函数,将在当前脚本所有同步任务执行完后执行,也就是过了随机数产生的秒数以后执行这里的console.log("成功"+result)。相同的,当test函数执行失败时,catch方法中指定的回调函数也会在test中的所有任务执行完后执行。
    上面提到的console.log("成功"+result)中的result其实就是等于" resolved",因为如果resolve函数和reject函数带有参数,那么它们的参数就会被传递给回调函数。通常情况下,reject函数的参数会使Error对象的实例,表示抛出的错误;resolve函数的参数可能是一个正常值,也有可能是另一个Promise实例,表示异步操作的结果可能是另一个异步操作。接下来就举一个执行若干个异步任务的例子.

    例2 (若干个异步任务)
    function add(input){
      return new Promise(function(resolve,reject){
        var result=input+input;
        console.log("add");
        setTimeout(function(){
          if(result<1000){
            resolve(result);
          }else {
            reject(result);
          }
        },500);
      })
    }
    
    function multiply(input){
      return new Promise(function(resolve,reject){
        var result=input*input;
        console.log("multiply");
        setTimeout(resolve,500,result);
      })
    }
    
    var p=new Promise(function(resolve,result){
      console.log("start new promise");
      resolve(10);
    })
    
    p.then(add).then(multiply).then(add).then(add).then(function(result){
      console.log("小于1000"+result);
    },function(result){
      console.log("大于1000"+result);
    });
    //输出:大于1000,1600
    

    上面这段代码首先定义了add和multiply,然后在add()函数中定义了成功和失败条件,就是当数字小于1000的时候未成功,大于1000的时候为失败。这里最重要的点是add和multiply都会返回一个Promise对象,所以在第一个Promise对象p运行结束后,会开始调用函数add,并将10传递给函数add当作input,此时multiply方法数就会等待add返回的这个新的promise对象发生变化,依次类推,最后的一个then方法指定的回调函数就会等待最后一个add返回的新的promise对象状态发生变化,如果变为Promise对象的状态为成功,就会调用console.log("小于1000"+result);,反之调用console.log("大于1000"+result);

    例3 (all和race)

    all方法将用于多个Promise实例,包装成一个新的Promise实例。

      var p=Promise.all([p1,p2,p3]);
    

    在使用all的情况下,p的状态由p1,p2,p3决定:

    • 只有p1,p2,p3的状态都变成Resolved,p的状态才会变成Resolved。
    • p1,p2,p3中只要有一个被Rejected,p的状态就变成了Rejected。
      var p1=new Promise(function(resolve,reject){
        setTimeout(resolve,1000,"p1 finish in 1s");
      });
    
      var p2=new Promise(function(resolve,reject){
        setTimeout(resolve,2000,"p2 finish in 2s");
      });
    
      Promise.all([p1,p2]).then(function(result){
        console.log("end"+result);
      });
      //直到2s后'p1 finish in 1s'和‘p2 finish in 2s’会被同时输出
    
    

    上例中,因为只有p1,p2都为Resolved,由它们包装成的新的Promise实例的状态才会变为Resolved,所以直到2秒以后,p1和p2的返回值才会被同时传递给新的promise实例的回调函数。

    race方法和all类似,同样将多个Promise实例包装成一个新的Promise实例。

      var p=Promise.race([p1,p2,p3]);
    

    在使用race的情况下,只要p1,p2,p3之中有一个率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。

    var p1=new Promise(function(resolve,reject){
      setTimeout(resolve,1000,"p1 finish in 1s");
    });
    
    var p2=new Promise(function(resolve,reject){
      setTimeout(resolve,2000,"p2 finish in 2s");
    });
    
    Promise.race([p1,p2]).then(function(result){
      console.log("end"+result);
    });
    //1秒后'p1 finish in 1s'和‘p2 finish in 2s’就会被同时输出
    

    上例中因为p1执行的较快,它的状态会率先变为Resolved,所以p1的返回值,就传递给了新的promise实例的回调函数。

    Promise应用于Ajax

    使用Promise简化Ajax异步处理:

    function ajax(method,url,data){
      var request=new XMLHttpRequest();
      return new Promise(function(resolve,reject{
        request.onreadystatechange=function(){
          if(request.readyState==4){
            if(request.status==200){
              resolve(request.responseText);
            }else {
              reject(request.status);
            }
          }
        });
      request.open(method,url);
      request.send(data);
    
      })
    }
    
    var p=ajax('POST',someUrl);
    p.then(function(text){
      alert(text);//成功获取到数据
    }).catch(function(status){
      alert(status); //请求数据失败,获得相应代码
    });
    
    

    这里的method可以是'GET'或者'POST',url是php或者asp文件的地址。

    总结

    关于Promise的大部分知识已经在本文涵盖到,不得不说廖雪峰和阮一峰老师的讲解很全面,但也许有些同学对于ES6并不熟悉,直接看阮一峰的Promise这章会有点吃力,所以笔者尽可能地在他们的基础上解释地更加细致一些,当然,想要全面地了解Promise的所有内容,还是要静下心来阅读ES6入门这本书。这是JS异步的第一篇,接下来仍会介绍别的JS异步的解决方案。

    推荐阅读

    相关文章

      网友评论

        本文标题:JavaScript学习笔记(5) 异步-- Promise

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