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的状态:
- pending(等待),初始状态,未知操作成功还是失败
- fulfilled(已完成),最终状态,操作成功,可以调用成功处理程序
- 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:
- 当和
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: 我没有买到菜,钱全花在打游戏了......
- 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的最终状态。
参数:
-
如果参数是一个值,将把这个值作为返回的promise的成功参数
-
如果这个值是一个 promise ,那么将返回这个 promise
-
如果是一个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类型)**
返回值:
- Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候
- 只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。
- 个人觉得有点像与操作了,当所有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 { <pending> }
,这就涉及到Promise的异步处理,文章后我会讲解
Promise.allSettled()
作用:
方法返回一个在所有给定的promise都已经fulfilled
或rejected
后的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 { <pending> }
,之后再setTimeout
,此时promise1 promise2才是操作完成的,value是111。
这也是和JavaScript的异步编程有关!
Promise的缺点
- 一旦新建Promise,它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
- 当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚开始还是即将完成)。
总结
终于写完了,自己也系统地学完了Promise,输出亦是一种学习的过程。
这篇文章主要是对Promise的理解和使用,至于Promise的实现,我也只是简单的看了大概,大家有兴趣可以继续深究源码。
此文章只代表个人见解,站在巨人的肩膀上看问题,感谢前辈们的贡献,如有错误,请指出!感谢大家!