了解Promise

http://liubin.org/promises-book/

同步回调以及异步回调

回调不一定是异步过程,回调是一个函数被作为一个参数传递到另一个函数里,在那个函数执行完后再执行。

  • 形式

    1
    2
    3
    4
    5
    6
    7
    function f1(callback) {
    // f1 的代码
    // f1 执行完成后,调用回调函数
    callback();
    }
    -------------执行代码变成如下
    f1(f2)
  • 同步回调

    1
    2
    3
    4
    5
    6
    7
    8
    function f1(callback){
    console.log('f1')
    f2() //调用f2函数并执行
    }
    function f2(){
    console.log('f2')
    }
    f1(f2) // f1 f2
  • 异步回调

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function f1(callback){
    setTimeout(function(){
    console.log('f1')
    callback()
    },2000)
    }
    function f2(){
    console.log('f2')
    }
    f1(f2) //f1 f2
    ------------如果不采用回调,输出结果为f2 f1
    fucntion f1(){
    setTimeout(function(){
    console.log('f1')
    },2000)
    }
    function f2(){
    console.log('f2')
    }
    f1()
    f2()
    // f2 (两秒后)f1

Promise

它的思想是,每一个异步任务立刻返回一个Promise对象,由于是立刻返回,所以可以采用同步操作的流程。这个Promises对象有一个then方法,允许指定回调函数,在异步任务完成后调用。

Promise接口的基本思想是,异步任务返回一个Promise对象。

  • Promise对象只有三种状态。
    • 异步操作“未完成”(pending)
    • 异步操作“已完成”(resolved,又称fulfilled)
    • 异步操作“失败”(rejected)
  • 这三种的状态的变化途径只有两种。
    • 异步操作从“未完成”到“已完成”
    • 异步操作从“未完成”到“失败”。
  • 这种变化只能发生一次,一旦当前状态变为“已完成”或“失败”,就意味着不会再有新的状态变化了。因此,Promise对象的最终结果只有两种。
    • 异步操作成功,Promise对象传回一个值,状态变为resolved
    • 异步操作失败,Promise对象抛出一个错误,状态变为rejected
  • Promise对象使用then方法添加回调函数。then方法可以接受两个回调函数,第一个是异步操作成功时(变为resolved状态)时的回调函数,第二个是异步操作失败(变为rejected)时的回调函数(可以省略)。一旦状态改变,就调用相应的回调函数。

then方法可以链式使用。

1
2
3
4
5
6
7
8
po
.then(step1)
.then(step2)
.then(step3)
.then(
console.log,
console.error
);

上面代码中,po的状态一旦变为resolved,就依次调用后面每一个then指定的回调函数,每一步都必须等到前一步完成,才会执行。最后一个then方法的回调函数console.logconsole.error,用法上有一点重要的区别。console.log只显示回调函数step3的返回值,而console.error可以显示step1step2step3之中任意一个发生的错误。也就是说,假定step1操作失败,抛出一个错误,这时step2step3都不会再执行了(因为它们是操作成功的回调函数,而不是操作失败的回调函数)。Promises对象开始寻找,接下来第一个操作失败时的回调函数,在上面代码中是console.error。这就是说,Promises对象的错误有传递性。

用法
  1. new Promise

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    先声明生成一个Promise实例
    var pro = new Promise(function(resolve,reject){
    setTimeout(function(){
    console.log('2秒后done')
    resolve('ok','hello') //resolve这里只能接受一个参数传递出去,第二个参数没用
    },2000)
    })
    然后再回调
    pro.then(function(val){ //这里的val就是上面的第一个参数'ok'
    console.log(val+''+'hello')
    },function(){
    console.log('error')
    })
    //ok hello
  2. Promise.resolve(value) && Promise.reject()

    1
    2
    3
    4
    5
    6
    7
    8
    Promise.resolve(30)
    ===等价于
    new Promise(function(resplve){
    resolve(30)
    })
    //value为空时,返回value为undefined的promise对象
    value为普通对象,返回状态为resolve的promise对象,起value为传入的参数
    value为promise对象,直接返回该promise对象
  3. Promise.then Promise.then(resolved,rejected)

    • resolved 和 rejected必须是函数,否则忽略
    • resolved必须在 Promise 的 Resolve 状态后调用,Promise 的 value 为其第一个参数,只能被调用一次。
    • rejected 必须在 Promise 的 Rejected 状态后调用,Promise 的 reason 为其第一个参数,只能被调用一次。
  4. Promise.catch === Promise.then(undefined.rejected)

  5. Promise.all:接收一个 promise 对象的数组作为参数,返回一个新的 promise 对象,特点如下:

    • 当数组内所有 promise 对象的状态为 Resolve,其状态才为 Resolve。
    • 当数组内有一个 promise 对象的状态为 Rejected,其状态就为 Rejected。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // 第一种情况
    var promise1 = Promise.resolve(1)
    var promise2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
    console.log(33)
    resolve('yes')
    }, 1000);
    });
    var promise3 = Promise.all([promise1, promise2]);
    promise3.then(function (val) {
    console.log('resolve', val);
    }, function (e) {
    console.log('reject', e);
    });
    // 一秒后返回resolve [1, "yes"]
    传的参数是一个数组,分别为promise1和promise2的参数。且按照最后一个值的时间一起打印!
    // 第二种情况
    var promise1 = Promise.resolve(1)
    promise.then(function(val){console.log(val)})
    var promise2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
    console.log(33)
    reject('error');
    }, 1000);
    });
    var promise3 = Promise.all([promise1, promise2]);
    promise3.then(function (val) {
    console.log('resolve', val);
    }, function (e) {
    console.log('reject', e);
    });
    // 1
    1秒后打印33 reject error

#####Promise:处理异步事件

  • 用回调获取结果(get请求,缺点:无法获取失败的结果)

    1
    2
    3
    4
    5
    $.get('./data.json',function(data,statusText,xhr){
    console.log(data)
    console.log(statusText)
    console.log(xhr) //封装后的对象
    }) //用jquery的get方法请求了这个路径

  • 用ajax请求(通过回调来解决异步的流程控制)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $.ajax({
    url:'./data.json',
    method:'get',
    success:function(data,statusText,xhr){ //等价于上面get请求
    console.log(data)
    },
    error:function(xhr,statusText,reason){
    console.log(xhr.status) //获取到状态码
    console.log(statusText)
    console.log(reason) //请求失败
    }
    })

    多层回调:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <body>
    用户:<span id="user"></span>
    <hr>
    分组:<span id="group"></span>
    <hr>
    其中第一个分组里面有成员:<span id="group_number"></span>
    <script src="./node_modules/jquery/dist/jquery.js"></script>
    <script src="./main.js"></script>
    </body>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    $.ajax({
    url:'./user.json',
    method:'get',
    success:function(data,statusText,xhr){
    console.log(data)
    $('#user').text(data.name)
    $.ajax({
    url:'./group.json',
    method:'get',
    success:function(data){
    console.log(data)
    $('#group').text([data[0].name,data[1].name].join('.'))
    $.ajax({
    url:'./group_number.json',
    method:'get',
    success:function(data){
    console.log(data)
    $('#group_number').text([data[0].name,data[1].name].join('.'))
    },
    error:function () {
    alert("im fine,fuck you")
    }
    })
    },
    error:function(){
    alert("im fine,fuck you")
    }
    })
    },
    error:function(xhr,statusText,reason){
    alert("im fine,fuck you")
    }
    })

  • promise的then方法,传有两个参数,一个成功,一个失败

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    let getuserPromise = $.get('./user.json')
    getuserPromise.then(function(data){
    console.log(data)
    $('#user').text(data.name)
    let getgroupPromise = $.get('./group.json')
    getgroupPromise.then(function(data){
    console.log(data) $('#group').text([data[0].name,data[1].name].join('.'))
    let getgroupnumberPromise = $.get('./group_number.json')
    getgroupnumberPromise.then(function(data){
    console.log(data)
    $('#group_number').text([data[0].name,data[1].name].join('.'))
    },function(){
    alert("im fine,fuck you")
    })
    },function(){
    alert("im fine,fuck you")
    })
    },function(){
    alert("im fine,fuck you")
    })

  • 一个简单的异步摇骰子过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function number(callback){
    setTimeout(function(){
    console.log('start')
    let random = Math.random()
    console.log(random)
    if(random>0.5){
    callback('big')
    return('big')
    }else{
    callback('small')
    return('small')
    }
    },1000)
    }
    let result = number(function(data){
    console.log(data)
    })

  • return new Promise(function(resolve, reject){}) //Promise里面一定是一个函数,这个函数一定会有两个参数,resolve和reject

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var a = new Promise(function(resolve,reject){
    console.log(1)
    resolve()
    })
    a.then(function(){
    console.log(2)
    })
    console.log(3)
    //打印顺序:1,3,2 给promise传一个函数,会立即执行这个函数打印出1,promise回调会走完流程后再去执行,尽管它没有任何延迟立即执行成功,也会先打印出3,再去打印2

    以上执行顺序是:1、5、2、3、6、4、7

  • promise里面的函数会立即执行,但是then之后的函数会在下一次的循环中执行。

#####小结

优点:避免了层层嵌套的回调函数,也让异步操作更加容易。

缺点:一旦建立Promise就会立即执行,中途无法取消 \ 如果不设置回调函数,Promise内部抛出的错误不会反应到外部 \ 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。