浅谈Promise对象

  1. 概念

    Promise对象用于一个异步操作的最终完成(或失败)及其结果值的表示。简单来说是用来处理异步请求。
    window.Promise是JS的一个内置对象。

    回调时代

    在以前处理异步的代码时,都是用的回调函数。以下面代码为例:
    假如当点击一个按钮时,发起一个AJAX请求,click函数接受两个回调函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    button.onclick = function(){
    click('GET','/xxx.js',function(xxx){
    console.log(xxx)
    },function(yyy){
    alert(yyy)
    })
    }
    function click(method,url,successCallback,errorCallback){
    $.ajax({
    type: method,
    url: url,
    success: function(responseText){
    successCallback(responseText)
    },
    error: function(xhr){
    errorCallback('请求错误,错误码为'+xhr.status)
    })

    这是一个典型的回调函数,不难发现在调用click函数,你得传四个参数,假如过几天你忘记了click函数里的代码,等你要调用这个函数时,你得看代码我应该传哪些参数,这些参数的顺序是什么?显然这样很不方便,接下来就要引入我们的主角Promise

    新时代,Promise来了

    Promise的思路是,click函数返回一个Promise对象,在这个返回对象上,你可以调用Promise原型的方法then,在then上挂两个回调函数,Promise给出了规定,若请求成功则执行then上的第一个回调,若请求失败则执行第二个回调。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    button.onclick = function(){
    let promise = $.ajax({type: 'GET',url: '/xxx.js',dataType: 'html'})
    promise.then(success,error)
    function success(responseText){
    console.log(responseText)
    }
    function error(xhr){
    alert('请求失败,错误码为'+ xhr.status)
    }
    }

    为了我们的代码更加具有可读性和可维护性,我们需要将数据请求和数据处理明确的区分开来。同时你都不需要给你函数起名字。在出现Promise之后, 用Promise写异步代码已成为一种趋势。

    1
    .then(success,error)

    此外,用then还有一个优点,它可以连续then多次,这是用回调无法比拟的。

    1
    promise.then(success,error).then(success1,error1)

    1
    2
    promise.then(success,error)
    promise.then(success1,error1)

    虽然请求成功之后,都会依次执行success和success1。这两段代码还是有点区别的。因为then会返回一个新的Promise对象。前段代码的第二个then的回调函数传的参数是第一个then的回调函数的返回值。而后段代码的两个then都是在同一个Promise对象上。

    Promise的几种状态

    Promise接口的基本思想是,异步任务返回一个Promise对象。
    Promise对象只有三种状态。

    1
    2
    3
    异步操作"未完成"(pending)
    异步操作"已完成"(fulfilled,又称resolved)
    异步操作"失败"(rejected)

    这三种的状态的变化途径只有两种。

    这种变化只能发生一次,如果是fulfilled状态,则Promise不能再转换成其它状态了,且必须会接受一个值。如果是rejected状态,则Promise也不能转换成其他状态,且必须抛出一个错误。

    Promise属性

    Promise自身的属性和方法

    1. Promise.length 长度属性,值为1,表示构造器参数的数目。

    2. Promise.prototype Promise的原型

    3. Promise.all()

    这个方法可以接受一个可迭代的对象作为参数,例如数组。Promise.all()表示所传的对象里所有的Promise对象变为fulfilled状态时,或者有一个Promise对象变为rejected时,便会返回一个Promise对象,这个对象才会去调用then方法,根据改变后的状态来判断执行then传的哪一个参数。
    该方法的应对场景,当一个AJAX请求,它的参数需要2个或者更多请求都有返回结果之后才能确定。

    4. Promise.race()

    该方法接受一个对象,当这个对象里的任何一个Promise对象状态变为fulfilledrejected时,并把该子Promise对象的成功返回值或拒绝详情作为参数传给父Promise绑定的句柄,同时返回一个Promise对象。

    Promise原型的属性和方法

    1. then方法

    1. 一个Promise必须提供一个then方法。
    2. then一定返回的是Promise对象。
    3. 对于一个Promise,then可以调用多次。
    4. then只接受两个为函数的参数。这两个参数是可选的。如果参数不是函数,就忽略它。这两个参数都只能调一次。第一个参数函数接收fulfilled状态的执行,第二个参数接收rejected状态的执行。
      fn().then(function(){}),不管fn函数里接受参数的代码时同步还是异步的,都要保证then是异步的。
    5. then的链式调用
      1
      2
      3
      4
      5
      6
      p.then(step1)
      .then(step2)
      .then(
      console.log,
      console.error
      )

    当p的状态变为fulfilled时,就依次调用后面每一个then指定的回调函数,每一步都等到前一步执行完成,才会执行。console.log只会显示step2的返回值,console.error只会显示step1和step2中任意一个发生的错误。若step1操作失败,则step2不会执行,而是把由step1抛出的错误传递给console.error打出。可见Promise的错误具有传递性。

    2. catch方法

    Promise.catch(onRejected)返回一个Promise对象,只处理拒绝的情况。它的行为与Promise.then(undefined,onRejected)相同。

    Promise的执行顺序

    我们知道Promise是异步的,而定时器接口也是异步的。那么它们哪个先执行?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    button.onclick = function(){
    setTimeout(function(){console.log(1),0})
    console.log(2)
    x().then(success)
    console.log(3)
    function x(){
    return new Promise(function(resolve,reject){
    console.log(4)
    resolve()
    })
    }
    function success(responseText){console.log(5)}
    }

    控制台打印的结果为

    1
    2
    3
    4
    5
    2
    4
    3
    5
    1

    我们可以推理出,x()是同步执行的,后面的then(success)是异步的。定时器也是异步的,虽然thensetTimeout都是属于‘下一批’执行的。但PromisesetTimeout稍微快一点。
    Promise的“下一批”属于microTask,定时器的”下一批”属于macroTask。这两个“下一批”并不是”同一批”。

    参考文章

    1. Promise对象(阮)
    2. Promise A+
    3. Promise mdn