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)
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(()=>{ resolve("我买到菜了!买了一条鱼和一斤牛肉") },1000) })
|
注意!promise的状态一经决定,无法改变,所以当用了resolve再用reject时reject不起效果,promise状态仍是fulfilled
Promise.prototype.then()
作用:
这个方法可以获取promise的状态,并进行结果处理。
它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
语法:
promise.then(value => { }, reason => { });
|
使用:
let order=new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve("我买到菜了!买了一条鱼和一斤牛肉") },1000) }) order.then((res)=>{ console.log(res); console.log("妈妈夸了一顿小明,并做了红烧鱼和爆炒牛肉"); },(err)=>{ console.log(err); console.log("妈妈打了一顿小明,并决定今晚出去吃"); })
我买到菜了!买了一条鱼和一斤牛肉 妈妈夸了一顿小明,并做了红烧鱼和爆炒牛肉
我没有买到菜,钱全花在打游戏了...... 妈妈打了一顿小明,并决定今晚出去吃
|
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) { });
|
使用:
let order=new Promise((resolve,reject)=>{ setTimeout(()=>{ reject("我没有买到菜,钱全花在打游戏了......") },1000) })
order.catch(err=>{ console.log("catch err: "+err); console.log("妈妈打了一顿小明,并决定今晚出去吃"); })
catch err: 我没有买到菜,钱全花在打游戏了...... 妈妈打了一顿小明,并决定今晚出去吃
|
tips:
- 当和
promise.then(undefined,onRejected)
同时存在时,onRejected优先
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() { });
|
使用:
let order=new Promise((resolve,reject)=>{ setTimeout(()=>{ reject("我没有买到菜,钱全花在打游戏了......")
},1000) })
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")){ }else{ }
|
示例:
let thenable = { then: (resolve, reject) => { resolve("thenble最终状态") } } let promise=Promise.resolve(thenable)
promise.then((res)=>{ console.log(res) },err=>{ } )
let promise1 = Promise.resolve(11111);
let promise=Promise.resolve(promise1)
promise.then((res)=>{ console.log(res) },err=>{ } )
let promise=Promise.resolve(2222)
promise.then((res)=>{ console.log(res) },err=>{ } )
|
Promise.reject()
作用:
返回一个带有拒绝原因的Promise
对象。注意此时的promise对象已是rejected的最终状态
和Promise.resolve()
的区别
Promise.reject
方法的参数,会原封不动地作为 reject 的参数,变成后续方法的参数。这一点与 Promise.resolve
方法不一致。
const thenable ={ then(resolve, reject){ reject('出错了'); } };
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
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, '小明做完作业了'); }); const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 100, '小东做完作业了'); }); let promise=Promise.race([promise1, promise2]); setTimeout(()=>{ console.log(promise) },1000);
Promise { '小东做完作业了' }
const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, '小明做完作业了'); }); const promise2 = new Promise((resolve, reject) => { setTimeout(reject, 100, '小东不想作业了'); }); 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, '小明做完作业了'); }); let promise2 = new Promise((resolve, reject) => { setTimeout(reject, 100, '小东不想作业了'); }); let promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, '小李做完作业了'); }); Promise.any([promise1, promise2,promise3]).then(res=>{ console.log(res) })
"小李做完作业了"
let promise1 = new Promise((resolve, reject) => { setTimeout(reject, 500, '小明不想作业了'); }); let promise2 = new Promise((resolve, reject) => { setTimeout(reject, 100, '小东不想作业了'); }); let promise3 = new Promise((resolve, reject) => { setTimeout(reject, 200, '小李不想作业了'); }); 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的实现,我也只是简单的看了大概,大家有兴趣可以继续深究源码。
此文章只代表个人见解,站在巨人的肩膀上看问题,感谢前辈们的贡献,如有错误,请指出!感谢大家!
源码实现
const PENDING = 'PENDING'; const FULFILLED = 'FULFILLED'; const REJECTED = 'REJECTED';
const resolvePromise = (promise2, x, resolve, reject) => { if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise #<Promise>')) } let called; if ((typeof x === 'object' && x != null) || typeof x === 'function') { try { let then = x.then; if (typeof then === 'function') { then.call(x, y => { if (called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, r => { if (called) return; called = true; reject(r); }); } else { resolve(x); } } catch (e) { if (called) return; called = true; reject(e) } } else { resolve(x) } }
class Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks= [];
let resolve = (value) => { if(value instanceof Promise){ return value.then(resolve,reject) } if(this.status === PENDING) { this.status = FULFILLED; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); } }
let reject = (reason) => { if(reason instanceof Promise){ return reason.then(resolve,reject) } if(this.status === PENDING) { this.status = REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach(fn=>fn()); } }
try { executor(resolve,reject) } catch (error) { reject(error) } }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; let promise2 = new Promise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }, 0); }
if (this.status === REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }, 0); }
if (this.status === PENDING) { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }, 0); });
this.onRejectedCallbacks.push(()=> { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0); }); } });
return promise2; } }
static resolve(data){ return new Promise((resolve,reject)=>{ resolve(data); }) }
static reject(reason){ return new Promise((resolve,reject)=>{ reject(reason); }) }
Promise.prototype.catch = function(errCallback){ return this.then(null,errCallback) }
Promise.prototype.finally = function(callback) { return this.then((value)=>{ return Promise.resolve(callback()).then(()=>value) },(reason)=>{ return Promise.resolve(callback()).then(()=>{throw reason}) }) }
Promise.all = function(values) { if (!Array.isArray(values)) { const type = typeof values; return new TypeError(`TypeError: ${type} ${values} is not iterable`) } return new Promise((resolve, reject) => { let resultArr = []; let orderIndex = 0; const processResultByKey = (value, index) => { resultArr[index] = value; if (++orderIndex === values.length) { resolve(resultArr) } } for (let i = 0; i < values.length; i++) { let value = values[i]; if (value && typeof value.then === 'function') { value.then((value) => { processResultByKey(value, i); }, reject); } else { processResultByKey(value, i); } } }); }
Promise.race = function(promises) { return new Promise((resolve, reject) => { for (let i = 0; i < promises.length; i++) { let val = promises[i]; if (val && typeof val.then === 'function') { val.then(resolve, reject); } else { resolve(val) } } }); }
function wrap(promise) { let abort; let newPromise = new Promise((resolve, reject) => { abort = reject; }); let p = Promise.race([promise, newPromise]); p.abort = abort; return p; }
const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('成功'); }, 1000); });
let newPromise = wrap(promise);
setTimeout(() => { newPromise.abort('超时了'); }, 3000);
newPromise.then((data => { console.log('成功的结果' + data) })).catch(e => { console.log('失败的结果' + e) })
|