ES6语法——Promise对象

一、概念

  Promise是异步编程的一种解决方案(解决回调地狱的问题),是一个能够获取异步操作信息的对象。Promise的内部保存着某个未来才会结束的事件(通常是一个异步操作)

二、特点

  1.Promise对象的状态不受外界影响

    Promise对象的状态由异步操作的结果决定当前处于pending(进行中)、fulfilled(已成功)还是rejected(已失败),任何其他操作都无法改变这个状态。

  2.状态改变不可逆

    一旦状态改变,就不会再变,任何时候都可以得到这个结果。promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。

  3.缺点

    (1)无法取消promise,一旦新建它就会立即执行,无法中途取消

    (2)如果不设置回调函数,promise内部抛出的错误,不会反应到外部

    (3)当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

  4.优点

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

三、基本用法 

  Promise对象是一个构造函数,接受一个函数为参数,这个函数的参数是resolve和reject,它们两个也是函数。

  resolve函数的作用是,将promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去,外部用then方法接收

  reject函数的作用是,将promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去,外部用catch方法接收

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

四、API详解

  1.promise.prototype.then()

    then方法是定义在原型对象promise.prototype上的,它是异步操作成功的回调函数,是resolve函数的外部接收器。参数是一个函数。

    工作原理:当promise对象执行异步操作成功时,会通过resolve函数向外传递操作结果,由then方法接收后,对该结果继续执行传入then方法内部的函数。

    then方法返回的是一个新的promise实例(注意,不是原来那个promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

function async(a,b) {
    return new Promise(function (resolve,reject) {
        //异步代码
        setTimeout(function () {
            //加入数据格式的判断
            if(a!=undefined && (typeof a) =="number"){
                //如果数据格式正确 则调用resolve匹配外部的then
                resolve(a+b);

            }else{
                //数据格式错误 调用reject  匹配外部的catch
                reject(new Error("a is not a number,你瞎啊!!!"));

            }

        },200)
    });
    
}
var promise=async(1,2);
promise.then(function (res) {
    if(res>2){  
        console.log("我是3分支");
      return async(res,3)
    }

    //如果上一个then返回了一个promise 那么可以后面继续跟着then
}).then(function (res) {
    if(res>4){
        console.log("我是4分支");
        return async(res,4);
    }
    
})

  2.promise.prototype.catch()

    catch方法的使用、原理同then方法,只是catch方法是失败的回调函数。

    注意:

      (1)如果没有使用catch()方法指定错误处理的回调函数,promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

      (2)catch方法只能有一个,有多个无意义,后面的catch并不会执行,catch方法通常写在最后

var user=new User("niujinghui","123456");
user.checkUsername()
.then(function (res) {
    return user.checkUserpwd();
})
 .then(function (res) {
        console.log("用户名密码都正确");
})
.catch(function (err) {
    if(err) throw err;
});

  3.promise.prototype.finally()

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

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

    上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

   finally方法源码

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

  4.promise.all()

    promise.all()用于将多个Promise实例包装成一个新的Promise实例。接受一个数组作为参数,如果该数组的元素不是promise对象实例,则使用Promise.resolve()方法将参数转为Promise实例再处理。另外,promise.all()方法的参数可以不是数组,但必须具有 iterator 接口,且返回的每个成员都是 promise 实例。

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

    上面代码中p的状态由p1、p2、p3决定,有以下两种情况:

      (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。(都成功,传返回值数组,如果没有返回值,会组成元素为undefined的数组)

      (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。(一失败,传reject的返回值)

    注意!!坑点来袭

      1、如果作为参数的 promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发promise.all()的catch方法。如果没有自己的catch方法,就会调用promise.all()的catch方法。

      2、使用all()方法时,传入的promise对象如果本身定义了then和catch方法,因为这两个方法都会返回一个新的promise实例,所以调用all方法得到的新promise状态一直都是成功。解释起来有点绕,结合下面的代码看就知道了。

const p1 = new Promise((resolve,reject)=>{
	let abc = 10
	let a = 10;
	let b = 20
	if(abc > 30){
		resolve(a+b) 
	}else{
		reject(b-a) 
	}
}).then(function(res){
	return res
}).catch(function(err){
	console.log(err)
})
const p2 = new Promise((resolve,reject)=>{
	let abc = 10
	let a = 10;
	let b = 20
	if(abc > 30){
		resolve(a+b+b) 
	}else{
		reject(b-a-a) 
	}
}).then(function(res){
	return res
}).catch(function(err){
	console.log(err)
})
const p3 = new Promise((resolve,reject)=>{
	let abc = 10
	let a = 10;
	let b = 20
	if(abc > 30){
		resolve(a*b) 
	}else{
		reject(b/a) 
	}
}).then(function(res){
	return res
}).catch(function(err){
	console.log(err)
})

const p=Promise.all([p1,p2,p3])
p.then(function(){
	console.log("正确")
}).catch(function(){
	console.log("错误")
})

    上面的代码在abc的值小于30时,就会执行reject方法,当调用promise.all()时,应该会输出错误。但是因为p1、p2、p3都有各自的then和catch方法,返回了新的promise实例,又因为这个新实例已经处于完成状态,所以变量p才会一直处于成功状态。(小炉写代码的时候还思考了好久,大坑!!!)

  5.promise.race()

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

    race方法的参数以及对参数中非promise对象的处理都和all方法一致,但是race方法返回的状态只和数组中最先改变状态的实例相同,那个最先改变的 promise 实例的返回值,就传递给p的回调函数。

    注意:

      (1)如果数组里面promise实例执行的速度一样,返回数组第一个promise执行的结果

      (2)如果数组里面promise实例执行的速度不一样,返回最快的promise执行的结果,结果是失败就会直接匹配race的catch方法

      (3)如果数组里面promise实例有成功、有失败的只要不是返回的结果,失败的就不影响

  6.promise.allsettled()

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

    该方法返回的新的 promise 实例,一旦结束,状态总是fulfilled,不会变成rejected

const promises = [ fetch(‘index.html‘), fetch(‘https://does-not-exist/‘) ];
const results = await Promise.allSettled(promises);

// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === ‘fulfilled‘);

// 过滤出失败的请求,并输出原因
const errors = results
  .filter(p => p.status === ‘rejected‘)
  .map(p => p.reason);

  7.promise.any()

    promise.any()跟promise.race()方法很像,只有一点不同,就是不会因为某个 promise 变成rejected状态而结束。如果所有的promise实例都是rejected,则返回AggregateError: All promises were rejected

    经过小炉自己试验,总结规则如下:

      (1)如果数组里面promise实例执行的速度不一样,执行最快的promise结果为成功则返回,为失败则按照这个规则找第二快的,依次类推。

      (2)如果数组里面promise实例执行的速度一样,则返回第一个成功的promise的返回值

  8.promise.resolve()

    作用:将现有对象转为 Promise 对象

    参数情况:

      (1)参数是一个 Promise 实例

        如果参数是 promise 实例,那么promise.resolve将不做任何修改、原封不动地返回这个实例。

      (2)参数是一个thenable对象

        thenable对象指的是具有then方法的对象,promise.resolve方法会将这个对象转为 promise 对象,然后就立即执行thenable对象的then方法

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});

       上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42。   

      (3)参数不是具有then方法的对象,或根本就不是对象

         如果参数是一个原始值,或者是一个不具有then方法的对象,则promise.resolve方法返回一个新的 promise 对象,状态为resolved。

      (4)不带有任何参数

         promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 promise 对象。

  9.promise.reject()

    promise.reject(reason)方法也会返回一个新的 promise 实例,该实例的状态为rejected。

const p = Promise.reject(‘出错了‘);
// 等同于
const p = new Promise((resolve, reject) => reject(‘出错了‘))

p.then(null, function (s) {
  console.log(s)
});

    注意:promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与promise.resolve方法不一致。

五、总结

  1.promise的作用:Promise是异步编程的一种解决方案(解决回调地狱的问题)

  2.无法取消promise,一旦新建它就会立即执行,无法中途取消

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

  4.当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

  5.promise.then()成功的回调函数,返回的是一个新的promise实例,可以使用链式编程,参数来自resolve函数

  6.promise。catch()失败的回调函数,只写一个,多写无意义,通常写在最后,参数来自reject函数

  7.promise.all()监听所有的promise实例状态,全成功则成功,一失败则失败

  8.promise.allsettled()监听所有的promise实例状态,等到所有这些参数实例都返回结果才结束,结束时状态总是fulfilled,不会变成rejected

  9.promise.any()不会因为某个 promise 变成rejected状态而结束。如果所有的promise实例都是rejected,则返回AggregateError: All promises were rejected

  10.promise.race()返回的状态只和数组中最先改变状态的实例相同,那个最先改变的 promise 实例的返回值,就传递给p的回调函数。

  11.promise.finally()不管 promise 对象最后状态如何,都会执行的操作

  12.promise.resolve()和promise.reject()都返回一个promise实例,但是promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数