• 浅谈Promise


    学习过JavaScript的人都知道,JavaScript是单线程作业,这样会有一个很大的缺陷,所有的Ajax,浏览器事件等,都是通过异步去完成。所谓的同步和异步最大的区别无非就是在于同步会阻塞后续代码的执行,然而异步则不会阻塞后续代码的执行。

    像setTimeout,setInterval,Ajax这些都是通过异步的回调去做的。拿setTimeout比方来说,及时是把时间设置成0,但是它依然是属于异步方法,任然不会阻塞后续代码的执行。

    说到头来无论是setTimeout,Ajax或是其他的异步操作都避免不了的就是回调函数。setTimeout中的function其实就是一个回调函数。

    setTimeout(function(){
      console.log("我是异步的")
    },0)
    

    看一个简单的小例子:

    console.log(1)
    console.log(2)
    console.log(3)
    setTimeout(function(){
      console.log(4)
    },0)
    setTimeout(function(){
      console.log(5)
    },0)
    console.log(6)
    console.log(7)
    console.log(8)
    

    结果:
    1,2,3,6,7,8,4,5

    如果先看最上面的三行代码,这三行代码是从上之下依次执行的。中间没有任何的异步操作,所以就会依次的打印出1,2,3。接下来就看下整体的代码,在代码中间掺杂了两个异步方法,由于异步操作不会阻塞代码所以就输出了1,2,3,6,7,8,4,5这样的结果。

    在做项目的过程中难免会出现一些情况。通过Ajax向后台去请求参数,在返回的参数里面又需要在另一个异步操作里面去使用,依此类推这样的需求如果在同一个页面里面出现N次也就出现了让我们最头疼的一个问题,这也就是我们常说的地狱式回调。

    举个例子:

    function fn(){
      $.ajax({
        url:'',
        success:funtion(data){
          $.ajax({
            url:"",
            data:data.n,
            success:function(data1){
              $.ajax({
                url:"",
                data:data1.n,
                success:function(){
                  ......
                }
              })
            }
          })
        }
      })
    }
    

    在上面的fn函数中使用了Ajax进行数据请求,服务器返回的参数中,需要用到返回参数里面的n值,才能得到需要的想要的参数,得到n值之后,使用n值作为参数,再次进行ajax请求,依此类推,这里还只是列举了三层,很有可能在实际的项目中会有更多的这样的需求,这样的代码无论是对于后期的维护还是代码的美观性,都是一件很让人头疼的事情,无非就是一场灾难。对于这种情况,还是有解决方案的。

    代码:

    function fn1(fn){
      $.ajax({
        url:'',
        success:funtion(data){
          fn() && fn(data.n)
        }
      })
    }
    function fn2(n,fn){
      $.ajax({
        url:'',
        data:{
          n:n
        },
        success:funtion(data){
          fn() && fn(data.n)
        }
      })
    }
    function fn3(n,fn){
      $.ajax({
        url:'',
        data:{
          n:n
        },
        success:funtion(data){
          fn() && fn(data.n)
        }
      })
    }
    fn1(function(n1){
      fn2(n1,function(n2){
        fn3(n2)
      })
    })
    

    同样的需求,通过回调函数的形式去解决就会方便很多,无论是代码的清晰度还是维护来说都会比较方便,即使这样是解决了一些问题,但仍然没有解决根本性的问题,还是需要通过回调函数去找到各个函数之间一一对应的关系,多多少少的也会带来一些小的困扰。

    为了解决类似这样的问题,EcmaScript6的发布推出了Promise对象,Promise的推出无非是所有学习JavaScript人的一个福音,Promise主要就是为了结果异步操作和地狱式回调函数的问题。

    从头到尾扯了了这么多,那么到底Promise到底是什么东西?主要能做什么?有什么特点?该怎么使用Promise对象?脑海中出现了一串的问号。

    Promise是什么?

    Promise简单的来说就是一个容器,里面保存着未来会结束的某个事件。一般来说会是一个异步操作。Promise可以获取到异步操作的消息。

    Promise的特点?
    • 对象的状态不会受到外界的影响。Promise代表的是一个异步的操作。一共有三种状态pending(进行中),resolved(已做完)和rejected(已失败),异步操作的结果,决定这Promise最终的状态。成功的则会使用resolved作为回调,如果失败则会使用rejected作为回调,任何的操作都无法改变其中的状态。
    • Promise的状态一旦改变,就永远不会再改变。Promise的改变只有两种情况,Pending –> rejected或者Pending-> resolved,Promise的最大特点也就是在此,如果你错过了他的返回结果,如果再想通过某种方法,去获得Promise的返回结果是获取不到的。
    Promise语法应用:

    Promise是一个对象,需要使用new得到一个新的Promise对象。

    代码示例:

    function imgs (url){
      return new Promise ((resolve, reject) => {
        var oImg = new Image;
        oImg.src = url;
        oImg.onload = function(){
          resolve(this);
        }
        oImg.onerror = function(){
          reject(new Error("图片加载失败"))
        }
      })
    }
    var oImg = imgs("http://aaronblog.vip/www/img/banner/banner-1517467815031.jpg");
    oImg.then((oimg) => {
      console.log(oimg)
    }).catch((err) => {
      console.log(err)
    })
    

    先分析一下上面的代码,在imgs函数中return出去了一个Promise 对象,然而在Promise对象里面,使用new方法新建了一个Image对象,为这个Image对象添加了一个src,并执行性onload,和onerror方法,当图片加载完成后使用resolve方法,并把this(this指向的是Image)作为参数作为传了出去,当图片加载失败的时候使用reject函数,并new一个Error返回出去。在执行imgs函数之后通过oImg变量接收,此时oImg对象得到的就是一个Promise对象。

    在Promise的原型上分别挂载着两个方法,then和catch,then代表成功,catch代表的是失败。所以在oImg.then的时候可以拿到传出来的Image对象。如果此时图片加载失败则会走catch,接收到的就是new Error的信息。

    需要注意的是,在then函数中一共两个回调函数,第一个是成功,第二个是失败,同样可以捕获到错误

    代码示例:

    var oImg = imgs("http://aaronblog.vip/www/img/banner/banner-1517467815031.jpg");
    oImg.then((oimg) => {
      console.log(oimg)
    },(err) => {
      console.log(err)
    })
    

    但是我们一般情况下不会这样去做,都是使用catch去捕获错误。这一点很重要。如果你非要使用这种方法也是可以的。不会出现问题。

    实例应用:

    function $ajax(data){
      return new Promise(resolve, reject){
        $.ajax({
          url:"",
          data:data,
          success:resolve,
          error:reject
        })
      }
    }
    var P1 = $ajax({a:1});
    P1.then((data) => {
      console.log("我成功了!")
    }).catch((err) => {
      console.log("我失败了!")
    })
    

    `

    上面ajax方法就是使用Promise对象进行了二次封装,当success的时候去使用resolve,error的时候则去执行reject方法,无论是失败还是成功,resolve,reject都可以接收到对应函数返回的结果。这样的话通过P1.then的方法就可以轻松的完成回调了。

    刚才在上面也有提到过Promise对象上挂载着两个方法then和catch,所以then和catch可以连续调用。

    代码示例:

    var P1 = $ajax({a:1});
    P1.then((data) => {
      console.log("我成功了!")
    }).catch((err) => {
      console.log("我失败了!")
    }).then(() => {
      console.log("我执行了!")
    }).then(() => {
      console.log("我是最后执行!")
    })
    

    通过上面的代码如果Ajax请求成功,会依次打印“我成功了!”,“我执行了!”,“我是最后执行!”,从这里可以分析出,这里的then是一步一步执行的,而不是异步执行的。记住这一点很重要。

    Promise实例的异步方法和then中返回的Promise的应用。

    第一种情况

    let p2 = new Promise ( ... )
    let p1 = new Promise ( (resolve, reject) => {
        resolve(p2)
    } )
    

    在p1中成功之后传入了p2,需要注意的是,p1中的resolve执行与不执行完全取决于p2的返回状态,如果p2返回的是成功,则p1.then会执行,否则是相反的。则p1则会走向catch方法。

    第二种情况

    let p3 = new Promise ( (resolve, reject) => {
        resolve()
    } )
    let p4 = new Promise ( ... )
    p3.then(
        () => return p4
    )
    

    在p3执行了then方法之后又return出去了p4此时,如果在p3后面再去使用then方法的话则会指向p4的then,而不再指向p3。catch则会同属于p3和p4。这种情况一般应用于,几个方法使用同样的操作去调整错误信息。

    以上是promise需要着重掌握的部分,下面在介绍一下关于promise的其他相关的API的使用

    上面说过Promise原型上挂载了两个方法then和catch,分别用来接受成功和失败的状态,但是除了这两个方法以外,还有其他的方法,由于这些方法不经常使用就简单的介绍一下。

    Promise其他API

    Promise.resolve()/Promise.reject()

    这两种方法会把一个对象封装成一个Promise对象,两者唯一不同的地方就是Promise.resolve()会根据参数的情况返回不同的Promise。

    需要注意的是:

    • 如果参数是Promise对象的话,则会直接返回传入的Promise对象。
    • 参数带有then方法,转换成Promise对象之后立即执行then方法。
    • 参数不带then方法,不是对象或没有参数,返回的则是Promise的失败状态。

    代码示例:

    var p1 = Promise.resolve([1, 2, 3]);
    p1.then(function(value) {
      console.log(value);
      //[1, 2, 3]
    });
    

    Promise.all

    这个方法接收的是一个数组,可以接收Promise对象,如果传入的不是Promise对象则,会默认的调用Promise.resolve()将其转换成Promise对象。在all()方法后面可以使用then方法,去接收参数,then方法里面存放的是一个数组,与传入的Promise对象的顺序是一一对应的关系。没有返回值则是undefined。
    需要注意的是在使用all方法的时候,如果传入的Promise对象,其中的任何一个失败了,则就会走向catch,将不会再等待其他Promise对象的返回结果。

    代码示例:

    var p1 = Promise.resolve(3);
    var p2 = 42;
    var p3 = new Promise(function(resolve, reject) {
      setTimeout(resolve, 100, 'foo');
    });
    Promise.all([p1, p2, p3]).then(function(values) {
      console.log(values);
    });
    // expected output: Array [3, 42, "foo"]
    

    上面代码p2不是一个promise对象,默认调用了Promise.resolve()转换成了promise对象,并返回了出去。

    Promise.race

    “比赛(竞速)”方法,这方法接收的同样也是一个数组,其传入的参数如果不是一个promise对象,则会调用Promise.resolve()方法,将其转换成Promise对象。

    这个方法如果传入的数组中的Promise对象,哪个先接收到结果就走入then成功,函数如果全部都失败的,则会走向catch。

    代码示例:

    var p1 = new Promise(function(resolve, reject) {
        setTimeout(resolve, 500, 'one');
    });
    var p2 = new Promise(function(resolve, reject) {
        setTimeout(resolve, 100, 'two');
    });
    Promise.race([p1, p2]).then(function(value) {
      console.log(value);
      //100
    });
    

    上面代码使用setTimeout模拟异步操作,由于p2的时间要比p1快很多所以输出的结果就是2了。

    Promise.done

    这个方法与Promise.then是类似的,同样可以提供resolved和rejected方法,也可以不提供任何的参数,其主要的目的是为了捕获then或catch尾端没有捕获到的错误。

    Promise.finally

    作为Promise的后续方法,无论是成功或是失败都会走这个方法。

    结语

    Promise对象可以高效的解决地狱式回调,使得代码变得更加清晰,易于维护。Promise虽然有他的好处,但是对于解决异步的解决方案,Es6/Es7还提出了其他的解决方案,以后有时间再详细的和大家说一下,最后感谢大家花费这么长时间阅读这篇文章。如果有什么异议,可以联系我或者在文章下方留言。

  • 相关阅读:
    Java中子类继承了父类的私有属性及方法吗?
    为什么静态成员、静态方法中不能用this和super关键字
    poj 3378 二维树状数组
    poj 3034 动态规划
    poj 2498 动态规划
    poj 2029 二维树状数组
    hdu 3280 动态规划
    hdu 2586 LCA
    poj 3689 树形dp
    poj 1947 树形dp
  • 原文地址:https://www.cnblogs.com/aaron---blog/p/9639681.html
Copyright © 2020-2023  润新知