JavaScript Promise浅析

JavaScript Promise

一.概念

Promise 是ES6对异步编程的一种解决方案,比传统的解决方案(如回调函数和事件)更合理更强大.
Promise 简单说就是一个容器,里面保存着一个尚未完成且预计在未来完成的异步操作

Promise是一个构造函数,用来创建一个Promise对象.

var promise=new Promise();

Promise对象代表一个异步操作,有三种状态

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

改变Promise对象的状态,有两种可能:

  • pending变为fulfilled
  • pending变为rejected

一旦状态改变,就不会再变,任何时候都可以得到这个结果.
在这里插入图片描述

二.Promise对象

1.回调地狱

回调函数一个嵌套一个,先执行这个外层的,然后在里层一层一层的去执行

$.ajax({
    type: "get",
    url: '',
    data: {},
    success: function () {
        $.ajax({
            type: "get",
            url: '',
            data: {},
            success: function () {
                $.ajax({
                    type: "get",
                    url: '',
                    data: {},
                    success: function () {
                        $.ajax({
                            type: "get",
                            url: '',
                            data: {},
                            success: function () {
                            }
                        });
                    }
                });
            }
        });
    }
});

看上面代码,恶不恶心?但是以前我们必须使用这种方式才能得到我们想要的.

2.Promise使用

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,解决了回调地狱!

const promise = new Promise(function (reslove, reject) {
    if(/*异步操作成功*/){
        reslove('成功');//从pending变为fulfilled
    }
    else{
        reject('失败');//从pending变为rejected
    }
});

Promise是一个构造函数,接受一个函数作为参数,被称为执行器(executor),该函数的两个参数分别是reslove和reject

reslove和reject是两个函数,由Javascript引擎提供,不用自己部署

2.1 reslove

reslove函数的作用是,将Promise对象的状态从"进行中"变成"成功"(即从pending变为resloved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去

2.2 reject

reject 函数的作用是,将Promise对象的状态从"进行中"变为"失败"(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去

2.3 then()

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数

promise.then(function (value) { 
    //success
}, function () { 
    //error
});

Promise实例的then方法:

  • 第一个参数是resolved状态的回调函数
  • 第二个参数(可选) 是rejected状态的回调函数(`也就是说捕获异常还可以通过then方法的第二个参数,但是一般不建议使用,直接使用catch捕获即可)

最后返回的是一个新的Promise实例

2.4 catch()

Promise的实例方法catch:用于指定发生错误时的回调函数

promise.catch(function (msg) {
    console.log(msg);
});

在Promise对象中:

如果该对象状态变为resolved,则会调用then()方法

如果该对象状态变为rejected,也就是异步操作抛出错误.则会调用catch()方法,另外,在then()里面抛出的异常,也会被catch()捕获

如果Promise状态已经变成resolved,再抛出异常错误是无效的

const promise = new Promise(function (reslove, reject
    reslove('成功');
    throw new Error();
});
promise.then(function (msg) {
    console.log(msg);
    //success
}, function (msg) {
    console.log(msg);
    //error
});
promise.catch(function (msg) {
    console.log(msg);
});

上述代码,Promise在resolve语句后面,再抛出异常,不会被捕获,因为Promise的状态一旦改变,就永久保持该状态.

Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止.所以,有这样的冒泡性质,就不需要在每个Promise对象中单独捕获异常了.

2.4.1 catch()本质
Promise.prototype.catch = function(fn){
    return this.then(null,fn);
}

其实catch的本质还是then()这个方法,只不过进行了封装,一个语法糖而已.

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名

2.5 then()第二个函数和catch()区别
  • then()第二个参数:Promise进入失败状态,或内部执行错误
  • catch()可以捕捉到进入成功状态的函数内部错误
const promise = new Promise(function (reslove, reject)
    throw new Error();
    reslove('成功');
});
promise.then(function (msg) {
    console.log(msg);
    //success
}, function (msg) {
    console.log("then()第二个函数",msg);
    //error
}).catch(function (msg) {
    console.log("catch方法",msg);
});

上面这段代码,我在Promise中抛出一个错误

then的第二个参数和catch捕获错误信息的时候会就近原则,如果是promise内部报错,reject抛出错误后,then的第二个参数和catch方法都存在的情况下,只有then的第二个参数能捕获到,如果then的第二个参数不存在,则catch方法会捕获到。

const promise = new Promise(function (reslove, reject) {
    throw new Error();
    reslove('成功');
});
promise.then(function (msg) {
    console.log(msg);
    //success
}).catch(function (msg) {
    console.log("catch方法",msg);
});

如果then()的第二个参数不存在,则会直接被catch()捕获!

当then()的第二个参数和catch同时存在时,还取决于Promise内部:

  • 如果是reject抛出错误,只有then第二个参数能捕获

  • 如果是resovle抛出成功,那么then的第二个参数不能捕获到,只有catch()才能捕获到!

const promise = new Promise(function (reslove, reject) {  
    //reslove('成功');
    //reject('失败');
});
promise.then(function (msg) {
    throw new Error();
    console.log(msg);
},function(msg){
    console.log("then方法",msg);
}).catch(function (msg) {
    console.log("catch方法",msg);
});

为什么说要推荐使用catch()方法,不推荐使用then()方法的第二个函数呢?因为使用catch()方法可以捕获到前面then()方法里执行中的错误,也更接近同步的写法(try/catch),所以,建议总是使用catch()方法,而不是使用then()的第二个参数

promise
    .then(function (data) {
        // success
    }, function (err) {
        // error
    });
promise
    .then(function (data) { 
        // success
    })
    .catch(function (err) {
        // error
    });
2.6 finally()

finally()方法用于指定不管Promise对象最后状态如何,都会执行的操作 该方法是ES2018引入标准的

promise.finally(()=>{});

这个finally方法,不管promise的最后状态,在执行完then()catch指定的回调函数以后,都会执行finally方法里指定的回调函数

server.listen(port)
    .then(function(){
    	//....
	})
	.finally(server.stop);
2.7 Promise对象本身是同步的

Promise是用来管理异步编程的,它本身不是异步的

var promise = new Promise(function(resolve, reject) {//这里是同步任务
    console.log(1);
    setTimeout(function(){
         console.log(2);
    },0);
    resolve();
})
promise.then(function() {//这里是异步任务
    console.log(3);
})
 console.log(4);  //打印 1,4,3,2

至于为什么为打印1,4,3,2,可以参考事件循环 Event Loop

2.8 Promise示例

将所有的异步,变成同步执行!

需求:

  • 2秒过后吃饭
  • 3秒过后做作业
  • 7秒过后上厕所
  • 1秒过后喝茶
let status = 0;
new Promise(function (reslove, reject) {
    setTimeout(() => {
        if (status == 0) {
            console.log("2秒过后吃饭");
            reslove("成功");
        }
        else {
            reject("失败");
        }
    }, 2000);
}).then(function (msg) {
    console.log("2秒到了" + msg);
    return new Promise(function (reslove, reject) {
        setTimeout(() => {
            console.log("3秒过后做作业");
            reslove("成功");
        }, 3000);
    });
}).then(function (msg) {
    console.log("3秒到了" + msg);
    return new Promise(function (reslove, reject) {
        setTimeout(() => {
            console.log("7秒过后上厕所");
            reslove("成功");
        }, 7000);
    });
}).then(function (msg) {
    setTimeout(() => {
        console.log("喝茶");
    }, 1000);
}).catch(function (msg) {
    console.log("失败了");
});

//2秒过后吃饭
//2秒到了成功
//3秒过后做作业
//3秒到了成功
//7秒过后上厕所
//喝茶

上面的几个过程中,无论哪个对象里面抛出异常,到最后,都可以通过catch()来捕捉,通过这种方式可以将所有 Promise 对象的错误合并到一个函数来处理,这样就解决了每个任务都需要单独处理异常的问题。

2.9 Promise链式调用

链式调用,大家可以想到jQuery,jQuery中之所以能够支持链式调用,大家都知道,每个方法最后都会return this,将当前的对象return出去,而咱们的Promise稍微有点不一样

Promise支持链式调用的原理是:每次对Promise调用then方法之后,都会创建并会返回一个新的Promise对象,可以继续用then方法调用

2.10 Promise优点缺点
1.优点

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,此外,Promise对象提供统一的接口,使得控制异步操作更加容易

2.缺点
  • 1.无法取消Promise,一旦新建它,就会立即执行,无法中途取消,

  • 2.如果不设置回调函数,Promise内部抛出的错误,不会反应到外部.

  • 3.当处于Pending状态时,无法得知目前进展到哪一个阶段

针对缺点的第1点,我们可以使用在Promise外层包一层函数,类似于懒加载的原理,需要才加载它,使用它.

function getPromise(){
    return new Promise(function(resolve,reject){
        resolve('成功');
    });
}

var promise=getPromise();
promise.then(function(msg){
    console.log(msg)
});

三.Promise.all()

Promise.all()是一个静态方法,属于Promise构造函数的方法

Promise.all可以将多个Promise实例包装成一个新的Promise实例

const p = Promise.all([p1, p2, p3]);
  • 1.它接收一个数组作为参数
  • 2.数组可以是Promise对象,也可以是其它的值,只有Promise会等待状态改变
  • 3.当所有的子Promise都完成,该Promise完成,返回值是全部值的数组.
  • 4.如果有任何一个失败,该Promise失败,返回值是第一个失败的子Promise的结果(短路特性).
 function execPromise() {
     var p1 = new Promise(function (res
         reslove('成功p1');
     });
     var p2 = new Promise(function (res
         reslove('成功p2');
     });
     var all = Promise.all([p1, p2]).then(function(data){
         console.log(data);
     }).catch(function (data) {
         console.log(data);
     });
 }
 
 execPromise();//["成功", "成功"]

上述代码:印证了第三点:当所有的子Promise都完成,该Promise完成,返回值是全部值的数组

function execPromise() {
    var p1 = new Promise(function (reslove, reject) {
        reslove('成功');
    });
    var p2 = new Promise(function (reslove, reject) {
        reslove('成功');
    });
    
    var p3 = new Promise(function (reslove, reject) {
        reject('失败');
    });
    var all = Promise.all([p1, p3, p2]).then(function (data) {
        console.log(data);
    }).catch(function (data) {
        console.log(data);
    });
}

 execPromise();//失败

上述代码:印证了第四点:如果有任何一个失败,该Promise失败,返回值是第一个失败的子Promise的结果.

Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示.

我们需要注意的是:

Promise.all获取的成功结果的数组顺序和Promise.all接收到的Promise对象数组顺序是一致的,也就是说p1在前面,即使是p1获取到的数据耗时比p2晚,也不会改变!这就带来了一个很大好处:在前端开发请求接口数据过程中,经常会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all,可以完美的解决这个问题

所以,Promise.all并不是并发执行的!

1.优点

可以并行的执行多个请求,等请求全部完成之后在做处理,一般场景有:有多个数据接口我需要等所有的接口返回数据之后,才干后面的事

2.缺点

在Promise.all中,只要有一个请求失败,那么就会直接返回失败,后面的无法执行,显然这并不是我们想要的结果,也就是我们常说的短路特性

既然有缺陷,我们就要弥补,可以通过给子Promise定义catch()的捕捉方法,然后返回resolve,那么Promise.all()的catch()就不会捕捉到~

function execPromise() {
    var p1 = new Promise(function (reslove, reject) {
        reslove('成功1');
    });
    var p2 = new Promise(function (reslove, reject) {
        reslove('成功2');
    });
    var p3 = new Promise(function (reslove, reject) {
        reject('失败');
    });
    
    Promise.all([p3, p1, p2].map(p => p.catch(function (err) {
        console.log("我是报错",err);
    }))).then(function (res) {
        console.log("都执行了"+res);
    }).catch(function (error) {
        console.log(error);
    });
}
execPromise();
//我是报错 失败
//都执行了,成功1,成功2

核心内容是map方法,其实就是给每个要执行的Promise对象加个catch()的处理,使用方式是通过map循环.map的每一项都是promise,catch方法返回值会被promise.reslove()包裹,这样传进promise.all的数据都是resolved状态的。

四.Promise.race()

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

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

其实,顾名思义,Promise.race就是赛跑的意思,也就是说Promise.race([p1, p2, p3]);里面那个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态,

原理比较简单,实际开发中,没有遇到什么类似场景,比较少用!

function execPromise() {
    var p1 = new Promise(function (reslove, reject) {
        reslove('成功1');
    });
    var p2 = new Promise(function (reslove, reject) {
        reslove('成功2');
    });
    var p3 = new Promise(function (reslove, reject) {
        reject('失败');
    });
    var all = Promise.race([p1, p3, p2]).then(function (data) {
        console.log(data);
    }, function (data) {
        console.log(data);
    });
}
execPromise(); //结果只会打印成功1

五.Promise.allSettled()

有时候我们不关系异步操作的结果,只关心这些操作有没有结束,这时,我们就可以考虑使用Promise.allSettled()了,如果没有这个方法,想要确保所有操作都结束.就很麻烦,Promise.all()无法做到这一点

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。

与Promise.all不同的在于.其不会短路,也就是说当Promise全部处理完成之后我们可以拿到每个Promise的状态,而不管其是否处理成功.


function execPromise() {
    var p1 = new Promise(function (reslove, reject) {
        reslove('成功1');
    });
    var p2 = new Promise(function (reslove, reject) {
        reslove('成功2');
    });
    var p3 = new Promise(function (reslove, reject) {
        reject('失败');
    });
    Promise.allSettled([p3, p1, p2]).then(function(data){
        console.log(data);
    });
}
execPromise();

打印结果如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值