1

在JavaScript中代码都是单线程执行的,因此JavaScript中所有的网络操作、浏览器事件都必须异步执行。在Promise之前JavaScript处理异步的方式都是回调函数。可以说callback的方式已是深入人心,那Promise又是解决什么问题的呢?看下面一段代码:

$.get('/getList',function(){
    $.get('/getCount',function(){
        $.get('/getDetail',function(){
            //....
        })
    })
})

这段代码就是传统的callback方式处理异步,可以看到刚刚3级嵌套代码层级就已经比较乱了,如果再加上一些逻辑代码那简直是无法直视。这就是我们常说的==回调地狱==问题。代码可读性低,难以维护,无法复用。

Promise解决回调地狱

Promise的基本用法

var promise = new Promise((resolve,reject)=>{
    setTimeout(function(){
        //这里异步操作已经执行完了,就可以通过resolve告诉外界可以进行其他操作了
        resolve('ok');
        //reject('no');
    },2000);
})

promise.then(res=>{
    console.log(res); // ok
},err=>{
    console.log(err); // no
})

通过Promise处理异步,先执行异步操作,不关心如何处理结果,通过Promise对象的返回成功还是失败,在将来的某个时刻执行结果处理函数。代码变得扁平化,且易读易维护。

  • resolve && reject

上面代码我们通过 resolve 方法把 Promise 的状态置为完成态(Resolved),这时 then 方法就能捕捉到变化,并执行“成功”情况的回调。
而 reject 方法就是把 Promise 的状态置为已失败(Rejected),这时 then 方法执行“失败”情况的回调(then 方法的第二参数)。

Promise实现多层回调

同样三级回调的代码我们再使用Promise重构一遍

new Promise((resolve,reject)=>{
    $.get('/getList',res=>{
        resolve(res);
    });
}).then(res=>{
    return new Promise((resolve,reject)=>{
        $.get('/getCount',res=>{
            resolve(res);
        });
    });
}).then(res=>{
    return new Promise((resolve,reject)=>{
        $.get('/getDetail',res=>{
            resolve(res);
        })
    });
}).then(res=>{
    //...
});

可以看到无论有多少层回调,都不用互相嵌套,只需要等待Promise对象“通知“执行即可。

Promise.all

当需要进行多步没有关联逻辑的异步操作时,可以使用Promise.all

Promise.all([
    $.get('/getList'),
    $.get('/getCount'),
    $.get('/getDetail')
]).then(([data1,data2,data3])=>{
    //then回调的参数是一个数组,顺组数序就是异步操作书写顺序
    console.log(data1,data2,data3);
},err=>{
    console.log(err);
});
  • all方法中的参数应该是Promise对象,因为ajax函数返回的对象就是promise对象所以这里是可以执行的;
  • 此种方式只适用于异步操作之间无逻辑关联;
  • 不论异步操作执行顺序如何,最后都会按照书写顺序返回(data1,data2,data3是按照异步操作书写顺序返回);
  • 如果如其中一个出错就会执行错误回调;

Promise.race

race的用法与all相似,不同点就是all会等所有异步操作全部执行完后再执行then回调,而race中只要有一个异步操作执行完成就立刻执行then回调。

Promise.race([
    new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log('函数一执行!');
            resolve('11');
        }, 1000);
    }),
    new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log('函数二执行!');
            resolve('22');
        }, 1200);
    })
]).then(result=>{
    console.log('then执行');
    console.log(result);
},err=>{
    console.log(err);
});

//执行结果为:
//函数一执行!
//then执行
//11
//函数二执行!

可以看到函数一执行明显要比函数二快,所以执行了函数一后立即执行了then回调,==注意这时函数二依然会执行==,但是执行后不会再触发then回调的执行。

Promise错误处理

  • 第一种方式,通过then
new Promise((resolve,reject)=>{
    //...
}).then(resSuccess=>{
    //成功
},resError=>{
    //失败
});
  • 第二种方式,catch捕获
new Promise((resolve,reject)=>{
    //...
}).then(resSuccess=>{
    //成功
}).catch(resError=>{
    //失败
});

==注==:catch方式更常用,因为不仅仅能捕获到reject传递的参数,还可以捕获到成功的回调中发生的错误。


(*Deferred对象及其方法)

jQuery 用 $.Deferred 实现了 Promise 规范。

function fn1() {
   var def = $.Deferred();
   //执行异步操作
   setTimeout(function () {
       console.log('函数1!');
       def.resolve('函数1执行完毕回调');
       //def.reject('函数1执行完毕回调');
   }, 1000);
   return def.promise();
}

//then方法第一个参数接收成功回调,第二个参数是接收失败回调
fn1().then(res => {
    console.log(res);
},err=>{
    console.log(err);
});

$.Deferred()方法返回一个对象,我们可以称之为Deferred对象,该对象包含一些方法如:then、done、fail 等。
jQuery就是用这个Deferred对象来实现Promise规范的。

对于多级回调的情况也可以使用then()方法进行链式调用:

function fn1() {
    var def = $.Deferred();
    //执行异步操作
    setTimeout(function () {
        console.log('执行函数1!');
        def.resolve('函数1执行完毕回调');
    }, 1000);
    return def.promise();
};

function fn2() {
    var def = $.Deferred();
    //执行异步操作
    setTimeout(function () {
        console.log('执行函数2!');
        def.resolve('函数2执行完毕回调');
    }, 1000);
    return def.promise();
};

fn1().then(res => {
    console.log(res);
    //可以链式调用的核心在于返回一个Deferred对象
    return fn2();
}).then(res => {
    console.log(res);
});

//执行函数1!
//函数1执行完毕回调
//执行函数2!
//函数2执行完毕回调

done() && fail()
done与fail的用法与then相似,实际就是一个语法糖。

function fn1() {
   var def = $.Deferred();
   setTimeout(function () {
       console.log('执行函数1!');
       def.resolve('函数1执行完毕回调');
       // def.reject('函数1执行失败回调');
   }, 1000);
   return def.promise();
};

fn1().then(function (res) {
    console.log(res);
}, function (err) {
    console.log(err);
});

fn1().done(function (res) {
    console.log(res);
}).fail(function (err) {
    console.log(err);
});

always()
Defferred对象上还有一个always方法,无论异步操作返回什么结果都会执行always回调。

 function fn1() {
    var def = $.Deferred();
    setTimeout(function () {
        console.log('执行函数1!');
        def.resolve('函数1执行完毕回调');
        // def.reject('函数1执行失败回调');
    }, 1000);
    return def.promise();
};

fn1().then(function (res) {
    console.log(res);
}, function (err) {
    console.log(err);
}).always(() => {
    console.log('无论成功失败都会进入这里');
});

由于Promise的出现,jQuery的Deferred对象已经很少使用了。一些其他用法在这也不一一举例了,如果有兴趣可以去官网详细了解。


巴斯光年
274 声望23 粉丝