"渣男"之Promise
一.为"渣男"正名
在之前写过一篇《NodeJS回调地狱》的文章,文章最后提到过使用Promise和async来解决"回调地狱"的问题,本文主要讲解Promise。如标题所示,我为什么把Promise叫做 "渣男"呢,从字面意思来看,Promise是承诺的意思,就是现在我给不了你结果,但是在未来我会给你一个结果,听起来就像一个渣男的言辞,这只是笔者打了个比方而已,请各位读者不要往心里去。那么我们言归正传,正是因为Promise的这种行为方式,就可以很好的解决回调地狱的问题,听起来可能有点懵,当你看完全文可能就不会有这种感觉了。
二.Promise状态机
Promise除了是个"渣男"外,它还是一个状态机,总共有三种状态: pending、resolved、rejected。pending是待决状态,说白了就是Promise还未给出任何的结果,即 "渣男"还在犹豫;resolved就是给出正确的结果,"渣男"给出一个好的结果,那么他就不是一个"渣男";rejected就是给出错误的结果,那么他就是一个名副其实的 "渣男"。我们通过代码来解释一下这三种状态:
function promTest() { /** * Promise的构造方法接收一个方法,方法有两个参数,第一个参数 * 用于返回正确的结果;第二个参数用于返回错误的结果;两个参数均为方法
* 只能接收一个参数 */ return new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 200) }) } let promise = promTest() //此时promTest中的Promise并没有给出结果, 此时的状态为pending console.log(promise) /** * promTest中过了200ms, Promise才给出结果, 这里过 * 300ms再次打印promise, 此时promise的状态为resolved */ setTimeout(() => { console.log(promise) }, 300)
在chrome控制台中执行的结果如下图:
接下来将代码promTest方法中的resolve('success')改为 reject(‘’fail),那么在chrome控制台的输出入下:
三.Promise异步编程
对Promise的执行then或者catch操作会返回一个新的Promise,该Promise最终状态根据then和catch的回调函数的执行结果决定,遵循着以下三个原则:
A.如果回调函数最终是throw,该Promise是rejected状态
B.如果回调函数最终是return,该Promise是resolved状态
C.如果回调函数最终retuen一个Promise,该Promise会和回调函数return的Promise状态保持一致
接下来,我们对三个原则分别展开阐述。
3.1.回调函数throw
function promTest() { /** * Promise的构造方法接收一个方法,方法有两个参数,第一个参数 * 用于返回正确的结果;第二个参数用于返回错误的结果 */ return new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 200) }) } let promise = promTest() //因为promise必然进入then方法,那么在该回调函数中 //抛出异常,promise2也是一个Promise,状态为rejected let promise2 = promise .then(res => { //抛出异常 throw new Error('fail') }) // promise和promise2的状态均为pending console.log(promise) console.log(promise2); setTimeout(() => { console.log(promise)
// promise2的状态得根据promse回调函数then来决定,而在then中直接抛出异常
console.log(promise2)
}, 300)
chrome的执行结果如下:
3.2.回调函数return
function promTest() { /** * Promise的构造方法接收一个方法,方法有两个参数,第一个参数 * 用于返回正确的结果;第二个参数用于返回错误的结果 */ return new Promise((resolve, reject) => { setTimeout(() => { reject('fail') }, 200) }) } let promise = promTest() //因为promise必然进入catch方法,但是在catch回调 //函数中直接return一个值,promise2的状态为resolved let promise2 = promise .catch(error => { return 'fail' }) // promise和promise2的状态均为pending console.log(promise) console.log(promise2); setTimeout(() => { console.log(promise) console.log(promise2) }, 300)
chrome的执行结果如下:
3.3.回调函数return一个Promise
function promTest() { /** * Promise的构造方法接收一个方法,方法有两个参数,第一个参数 * 用于返回正确的结果;第二个参数用于返回错误的结果 */ return new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 200) }) } let promise = promTest() // 因为promise必然进入then方法,但是在then回调 // 函数中直接return一个Promise,promise2的状态 // 与回调函数中的返回的Promise的状态一致 let promise2 = promise .then(res => { return new Promise((resolve, reject) => { reject('fail') }) }) // promise和promise2的状态均为pending console.log(promise) console.log(promise2); setTimeout(() => { console.log(promise) console.log(promise2) // promise2的状态为reject }, 300)
chrome的执行结果如下:
四.Promise解决回调地狱问题
我们还是沿用《NodeJS回调地狱》中的那个面试案例来说(如果不知道那个案例的,建议点击链接过去看看),我们通过Promise来改造一下,代码如下所示:
// 面试函数 function interview(round) { return new Promise((resolve, reject) => { setTimeout(() => { if(Math.random() > 0.5) { resolve('success') }else { let error = new Error('fail') error.round = round reject(error) } }, 200) }) } interview(1) //第一轮面试 .then(res => { /** * interview返回一个Promise, 会得到一个新的Promise, * 新的Promise的状态和interview(2)的状态一致 */ console.log('第一轮面试通过') return interview(2) }).then(res => { console.log('第二轮面试通过') return interview(3) }).then(res => { console.log('面试全部通过') }).catch(error => { console.log('第 ' + error.round + ' 轮面试失败.') })
chrome的执行多次的结果如下:
五.Promise并发执行
现将上面的案例改一下,例如现在去两家公司面试,那么该如何处理呢?
// 面试函数 function interview(name) { return new Promise((resolve, reject) => { setTimeout(() => { if(Math.random() > 0.4) { resolve('success') }else { let error = new Error('fail') error.name = name reject(error) } }, 200) }) } Promise.all([ interview('baidu'), interview('alibaba') ]).then(res => { console.log('两家公司均面试成功.') }).catch(error => { //谁先reject,谁先进入该回调函数 console.log('面试失败在 ' + error.name + ' 公司上'); })
执行的结果各位读者可以自行测试,我这里就不展示了。