JavaScript_Promise的总结
发布于 3 年前 作者 xiongyong 2293 次浏览 来自 分享

JavaScript-Promise总结

前言:

​ 最近在做项目对接后端接口时,遇到了一个bug,和promise有关,于是在网上找资料,找啊找,发现自己对Promise的了解还是很生疏的,于是就开始了对promise的学习,刚好最近也想开始写一些文章来记录自己的学习路程,也希望可以帮助到大家。然后,就开始了我前端之路的第一篇知识输出文章,请大家多多指教!

正文:

Promise概述

​ 异步编程是JavaScript一大特点,建议大家先了解一下JavaScript的异步编程实现,这里推荐一篇文章—JavaScript异步编程 - 熊建刚的文章 - 知乎 https://zhuanlan.zhihu.com/p/26567159

​ Promise是实现异步编程的一种解决方案,比传统的的解决方案(回调函数和事件)更加先进,先进在哪里呢?上代码!

//传统异步编程方式
setTimeout(function(){
    console.log("doThing1");
    setTimeout(function(){
        console.log("doThing2");
        setTimeout(function(){
            console.log("doThing3");
            setTimeout(function(){
                console.log("doThing4");
            },1000)
        },1000)
    },1000)
},1000)

//promise实现
let p1=new Promise((resolve,reject)=>{
    setTimeout(function () {
            console.log("doThing1");
            resolve();
        }, 1000);
})
let p2=new Promise((resolve,reject)=>{
    setTimeout(function () {
            console.log("doThing1");
            resolve();
        }, 1000);
})v
let p3=new Promise((resolve,reject)=>{
    setTimeout(function () {
            console.log("doThing1");
            resolve();
        }, 1000);
})
let p4=new Promise((resolve,reject)=>{
    setTimeout(function () {
            console.log("doThing1");
            resolve();
        }, 1000v);
})
p1.then(p2).then(p3).then(p4);

上面的代码,我们想按照顺序去做4件事情,如果按照传统的方式来实现很容易造成“函数瀑布”,这样的代码被称为“回调地狱”,看起来就很头疼,像promise这样实现更为直观,维护起来也方便很多,这仅仅是代码书写上的优势,在一些错误处理,顺序性等等都比传统异步编程更为优化,这里就不做深究了。

​ “Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息”。一个 Promise对象会将异步操作的最终结果和结果的处理程序关联起来(成功or失败),那么自然就要求promise需要有状态,我们才可以知道promise现在是成功了还是失败了。

Promise的状态:

  1. pending(等待),初始状态,未知操作成功还是失败
  2. fulfilled(已完成),最终状态,操作成功,可以调用成功处理程序
  3. rejected(已失败),最终状态,操作失败,可以调用失败处理程序

这里引入一个例子:小明的妈妈在做家务没空买菜,那么她叫小明去买菜,这个任务就是一个promise,好,小明去买菜了,小明买菜这个过程是一个异步操作,那么小明买菜的结果是什么呢?这就是说这个命令有没有完成?有两种情况,第一种是小明成功买到菜了,接下来小明妈妈就是做饭啦,第二种情况,小明把钱拿去打游戏了没买到菜,接下来小明妈妈就是把他打一顿啦…

接下来讲promise的使用都会用到这个例子。

Promise的方法

Promise()-promise的构造函数

作用:

创建一个新的 Promise 对象。该构造函数主要用于包装还没有添加 promise 支持的函数。

Promise接受函数两个参数(resolve, reject),当异步任务成功时,调用第一个参数resolve,将promise对象的状态设为fulfilled,并返回成功值;失败时将调用第二个参数reject将promise对象的状态设为rejected,并返回失败原因;

使用:
let order=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        //小明买菜ing
        // (1)小明一路破关斩将,抵御各种诱惑,成功买到一条鱼和一斤牛肉
        //(2)小明看到菜市场旁边的游戏厅,被里面的游戏所吸引,把钱全花在打游戏那里了,没有买到菜
        
        //小明回到家告诉妈妈
        resolve("我买到菜了!买了一条鱼和一斤牛肉")
        //reject("我没有买到菜,钱全花在打游戏了......")    //敢作敢当....
        
    },1000)//这里用setTimeout来模拟小明去买菜这个异步任务
})

注意!promise的状态一经决定,无法改变,所以当用了resolve再用reject时reject不起效果,promise状态仍是fulfilled

Promise.prototype.then()

作用:

这个方法可以获取promise的状态,并进行结果处理。

它最多需要有两个参数:Promise 的成功和失败情况的回调函数。

语法:
promise.then(value => {
  // fulfillment
}, reason => {
  // rejection
});
使用:
let order=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        //小明买菜ing
        // (1)小明一路破关斩将,抵御各种诱惑,成功买到一条鱼和一斤牛肉
        //(2)小明看到菜市场旁边的游戏厅,被里面的游戏所吸引,把钱全花在打游戏那里了,没有买到菜
        
        //小明回到家告诉妈妈
        resolve("我买到菜了!买了一条鱼和一斤牛肉")
        //reject("我没有买到菜,钱全花在打游戏了......")    //敢作敢当....
    },1000)//这里用setTimeout来模拟小明去买菜这个异步任务
})
order.then((res)=>{
    console.log(res);
    console.log("妈妈夸了一顿小明,并做了红烧鱼和爆炒牛肉");
},(err)=>{
    console.log(err);
    console.log("妈妈打了一顿小明,并决定今晚出去吃"); //好像小明这样做确实不错,可以出去吃了
})
//输出结果:
我买到菜了!买了一条鱼和一斤牛肉
妈妈夸了一顿小明,并做了红烧鱼和爆炒牛肉

//如果是reject("我没有买到菜,钱全花在打游戏了......")
我没有买到菜,钱全花在打游戏了......
妈妈打了一顿小明,并决定今晚出去吃
tips:

由于 then 和 Promise.prototype.catch()方法的返回值都是 promise,它们可以被链式调用——这同时也是一种被称为__复合__( composition) 的操作。

//类似  p1.then(p2).then(p3).then(p4).catch(p5).then(p6).......

Promise.prototype.catch()

作用:

​ 这个方法主要获取promise错误状态,并进行结果处理。其实和promise.then(undefined,onRejected)时一样的,不过promise.then优先(等下会有示例)。

语法:
promise.catch(function(reason) {
   // 拒绝z
});
使用:
let order=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        //小明买菜ing
        // (1)小明一路破关斩将,抵御各种诱惑,成功买到一条鱼和一斤牛肉
        //(2)小明看到菜市场旁边的游戏厅,被里面的游戏所吸引,把钱全花在打游戏那里了,没有买到菜
        
        //小明回到家告诉妈妈
       // resolve("我买到菜了!买了一条鱼和一斤牛肉")
        reject("我没有买到菜,钱全花在打游戏了......")    //敢作敢当....
    },1000)//这里用setTimeout来模拟小明去买菜这个异步任务
})

order.catch(err=>{
    console.log("catch err:  "+err);
    console.log("妈妈打了一顿小明,并决定今晚出去吃");  
})
//输出结果:
catch err:  我没有买到菜,钱全花在打游戏了......
妈妈打了一顿小明,并决定今晚出去吃
tips:
  1. 当和promise.then(undefined,onRejected)同时存在时,onRejected优先
//构造Promise order同上
order.then(undefined,err=>{
    console.log("then err:  "+err);
})
.catch(err=>{
    console.log("catch err:  "+err);
})
//输出结果:
then err:  我没有买到菜,钱全花在打游戏了......
  1. catch可以捕获then()里面的函数
order.then(undefined,err=>{
    console.log("then err:  "+err);
    throw Error("then出错了")
})
.catch(err=>{
    console.log("catch err:  "+err);
})
//输出结果
then err:  我没有买到菜,钱全花在打游戏了......
catch err:  Error: then出错了

3.当在链式调用时,catch可以捕获前面任意一个then中的错误,这也就是说我们写代码的时候最好可以用上catch

//类似  p1.then(p2).then(p3).then(p4).then(p5).catch(err).......
//catch可以捕获p2 p3 p4 p5中任意一个中的错误

Promise.prototype.finally()

作用:

当promise结束是,无论最终状态如何,fulfilled还是rejected,都会执行指定回调函数

语法:
promise.finally(function() {
   // 返回状态为(resolved 或 rejected)
});
使用:
let order=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        //小明买菜ing
        // (1)小明一路破关斩将,抵御各种诱惑,成功买到一条鱼和一斤牛肉
        //(2)小明看到菜市场旁边的游戏厅,被里面的游戏所吸引,把钱全花在打游戏那里了,没有买到菜
        
        //小明回到家告诉妈妈
       // resolve("我买到菜了!买了一条鱼和一斤牛肉")
        reject("我没有买到菜,钱全花在打游戏了......")    //敢作敢当....

        //promise的状态一经决定,无法改变,所以当用了resolve再用reject时reject不起效果,promise状态仍是fulfilled
    },1000)//这里用setTimeout来模拟小明去买菜这个异步任务
})

order.then((res)=>{
    console.log(res);
    console.log("妈妈夸了一顿小明,并做了红烧鱼和爆炒牛肉");
},(err)=>{
    console.log(err);
    console.log("妈妈打了一顿小明,并决定今晚出去吃");   //好像小明这样做确实不错,可以出去吃了
}).catch((err)=>{
    //获取前面出现的错误,有可能妈妈做饭的时候鱼被猫吃了.......
}).finally(()=>{
    console.log("无论结果如何,最终都是要吃饭的!事已至此,先吃饭吧。。。。");
})

//输出结果
我没有买到菜,钱全花在打游戏了......
妈妈打了一顿小明,并决定今晚出去吃
无论结果如何,最终都是要吃饭的!事已至此,先吃饭吧。。。。

Promise.resolve()

作用:

返回一个带有成功参数的Promise对象。注意此时的promise对象已是fulfilled的最终状态。

参数:
  1. 如果参数是一个值,将把这个值作为返回的promise的成功参数

  2. 如果这个值是一个 promise ,那么将返回这个 promise

  3. 如果是一个thenable,返回的promise会“跟随”这个thenable的对象,采用它的最终状态。

    thenable:

    thenable是任何含有then()方法的对象或函数,作用使promise的实现更具有通用性

    类似:

    let thenable = {
      then: (resolve, reject) => {
        resolve(thenable)
      }
    }
    

    判断一个对象是不是thenable,用到类型检查,也称为鸭式辩型

    if(p!=null&&(typeof p==="object"||typeof p==="function")&&(typeof p.then==="function")){
        //p是一个thenable
    }else{
        //p不是一个thenable
    }
    
示例:
let thenable = {
    then: (resolve, reject) => {
      resolve("thenble最终状态")
    }
  }
  
let promise=Promise.resolve(thenable)  

promise.then((res)=>{
    console.log(res)
},err=>{
    //用不上
}
)
//打印结果
//thenble最终状态

let promise1 = Promise.resolve(11111);

let promise=Promise.resolve(promise1)  

promise.then((res)=>{
    console.log(res)
},err=>{
    //用不上
}
)
//打印结果
//11111

let promise=Promise.resolve(2222)  

promise.then((res)=>{
    console.log(res)
},err=>{
    //用不上
}
)
//打印结果
//22222

Promise.reject()

作用:

​ 返回一个带有拒绝原因的Promise对象。注意此时的promise对象已是rejected的最终状态

Promise.resolve()的区别

Promise.reject 方法的参数,会原封不动地作为 reject 的参数,变成后续方法的参数。这一点与 Promise.resolve 方法不一致。

  //Promise.resolve() 和Promise.reject()的区别    
  const thenable ={
    then(resolve, reject){
       // resolve("成功了")
     reject('出错了');
  }
  };
  

  //Promise.resolve() 和Promise.reject()的区别    
  //Promise.resolve(thenable)
  Promise.reject(thenable)
  .then(res=>{
      console.log("then res:  "+res)
    console.log(res===thenable)
  })
  .catch(err =>{
      console.log("catch err:  "+err)
      console.log(err===thenable)
  })
//打印结果
catch err:  [object Object]
true

//当执行Promise.resolve(thenable)
catch err:  出错了
false

对resolve(thenable)结果解析:

当thnable传入resolve时,将立即执行thnable的then方法,resolve返回它的最终状态reject(‘出错了’),此时resolve返回的promise参数并不是一个thnable了,而是“出错了”
而当thnable传入rejected时,并不会执行thnable的then方,而是原封不动将thnable作为resolve返回的promise参数

Promise.all()

作用:

整合多个promise示例,返回最终一个promise实例

参数:

一个promise的iterable类**(注:Array,Map,Set都属于ES6的iterable类型)**

返回值:

  1. Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候
  2. 只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。
  3. 个人觉得有点像与操作了,当所有promise都fulfilled时,返回的promise才是fulfilled,完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 promise 值);而当任意一个promise失败时,返回的promise时rejected,Promise.all()异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。
let promise1=Promise.resolve("小明成功买到菜了");
let promise2=Promise.reject("小明没晾衣服");
let promise3=Promise.resolve("小明刷完高数了");

Promise.all([promise1, promise2, promise3]).then(res => {
    console.log(res); 
  });
//打印结果
[ '小明成功买到菜了', '小明晾衣服了', '小明刷完高数了' ]

let promise1=Promise.resolve("小明成功买到菜了");
let promise2=Promise.reject("小明没晾衣服");
let promise3=Promise.resolve("小明刷完高数了");

let promise=Promise.all([promise1, promise2, promise3]);
setTimeout(()=>{
    console.log(promise)
},0)  
//打印结果
Promise { <rejected> '小明没晾衣服' }

至于第二个例子我为什么要用setTimeout,大家可以保留疑问,假如直接打印,结果会是Promise { &lt;pending&gt; },这就涉及到Promise的异步处理,文章后我会讲解

Promise.allSettled()

作用:

​ 方法返回一个在所有给定的promise都已经fulfilledrejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

​ 如果有多个彼此不依赖的异步任务完成时,然后你要知道所有promise的结果,又不想一个一个地获取时,可以用到这个。相反,假如你的异步任务有依赖则要用Promise.all()

参数:

​ 和Promise.all()一致

示例:
let promise1=Promise.resolve("小明成功买到菜了");
let promise2=Promise.reject("小明没晾衣服");
let promise3=Promise.resolve("小明刷完高数了");

Promise.allSettled([promise1, promise2, promise3]).then((result)=>{
    result.forEach((item)=>{
        console.log(item)
    })
})
//打印结果
[
  { status: 'fulfilled', value: '小明成功买到菜了' },
  { status: 'rejected', reason: '小明没晾衣服' },
  { status: 'fulfilled', value: '小明刷完高数了' }
]

Promise.race()

作用:

返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

race-比赛,看哪个promise最先被定义最终状态,则返回该结果

参数:

​ 和Promise.all()一致

示例:
const promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 500, '小明做完作业了');  //小明做完作业花费了500ms
  });
  
  const promise2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, '小东做完作业了');  //小东做完作业花费了100ms
  });
  
  let promise=Promise.race([promise1, promise2]);
  setTimeout(()=>{
    console.log(promise)
  },1000);

//打印结果
Promise { '小东做完作业了' }

const promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 500, '小明做完作业了');  //小明做完作业花费了500ms
  });
  
  const promise2 = new Promise((resolve, reject) => {
    setTimeout(reject, 100, '小东不想作业了');  //小东100ms后就说不做作业了...
  });
  
  let promise=Promise.race([promise1, promise2]);
  setTimeout(()=>{
    console.log(promise)
  },1000);
//打印结果
Promise { <rejected> '小东不想作业了' }

Promise.any()

作用:

Promise.all()相反,及相当于做或操作,只要有一个promise成功了,那么最终结果就成功(只返回第一个成功的promise),如果都不成功,最终promise才为rejected。

参数:

​ 和Promise.all()一致

示例:
let promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 500, '小明做完作业了');  //小明做完作业花费了500ms
  });
  
  let promise2 = new Promise((resolve, reject) => {
    setTimeout(reject, 100, '小东不想作业了');  //小东100ms后就说不做作业了...
  });
  let promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, '小李做完作业了');  //小明做完作业花费了500ms
  });
  Promise.any([promise1, promise2,promise3]).then(res=>{
      console.log(res)
  })
//打印结果
 "小李做完作业了"

let promise1 = new Promise((resolve, reject) => {
    setTimeout(reject, 500, '小明不想作业了');  //小明500ms后就说不做作业了...
  });
  
  let promise2 = new Promise((resolve, reject) => {
    setTimeout(reject, 100, '小东不想作业了');  //小东100ms后就说不做作业了...
  });
  let promise3 = new Promise((resolve, reject) => {
    setTimeout(reject, 200, '小李不想作业了');  //小明100ms后就说不做作业了...
  });
  Promise.any([promise1, promise2,promise3]).catch(err=>{
  console.log(err.message)})
//打印结果
 "All promises were rejected"

Promise的异步执行

我们先来看一段代码!

let promise1 = Promise.resolve(11111);

let promise2 = promise1.then(value => {
  console.log("promise1的value: " + value);
  return value;
});

console.log(promise2);
    
setTimeout(() => {
   console.log(promise2);
});
//打印结果:
Promise { <pending> }
promise1的value: 11111
Promise { 11111 }

是不是和你的预期不一样,这就是Promise的异步执行;

我们来分析一下:

​ 其实代码执行到 let promise1 = Promise.resolve(11111); Promise.resolve不会在时不会立即执行的,这是交给异步来完成的,那么就顺着执行下面的代码了.

​ 到let promise2 = promise1.then时,此时的promise1的状态还是pending,所以promise2也是pending,此时value是undefined的。

​ 然后就是执行console.log(promise2),所以打印的第一个是Promise { &lt;pending&gt; },之后再setTimeout,此时promise1 promise2才是操作完成的,value是111。

这也是和JavaScript的异步编程有关!

Promise的缺点

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

总结

​ 终于写完了,自己也系统地学完了Promise,输出亦是一种学习的过程。
​ 这篇文章主要是对Promise的理解和使用,至于Promise的实现,我也只是简单的看了大概,大家有兴趣可以继续深究源码。
​ 此文章只代表个人见解,站在巨人的肩膀上看问题,感谢前辈们的贡献,如有错误,请指出!感谢大家!

5 回复

promise我还没学,看完这篇文章感觉学到了不少东西~~

promise,算是es6新增的不错的编程风格吧

写的很清晰很棒,又学习了一遍~

这个Promise用法的研究和学习好有趣,值得学习

小明可真优秀 啥都会

回到顶部