对于Promise的定义和根本应用,可参考红宝书和MDN。
在弄清楚Promise为何物之前,首先要明确它为何存在:
- Promise不是新的语法,而是对回调函数这种异步编程的形式进行的改良。
- Promise将嵌套调用改为链式调用,减少了可浏览性和可维护性;
Promise与回调函数
先说论断:回调函数是JS实现异步编程的形式之一,而Promise是解决回调天堂的形式之一。
在JavaScript的世界中,所有代码都是单线程执行的。因为这个“缺点”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。
以网络申请为例,如果须要在获取前一个申请的数据之后,再发动下一个申请,那么可能会写成如下模式:
ajax1(url1, () => { doSomething1() ajax2(url2, () => { doSomething2() ajax3(url3, () => { doSomething3() }) })})
如此上来,如果嵌套更多回调函数,就会造成常说的“回调天堂”。
回调天堂的毛病很显著:
- 代码耦合,浏览性差,不好保护;
- 无奈应用try catch,就无奈排错。
而Promise能够很好的解决“回调天堂”问题:
ajax1(url1).then(res => { doSomething1() return ajax2(url2)}).then(res => { doSomething2() return ajax3(url3)}).then(res => { doSomething3()}).catch(err => { console.log(err)})
能够看到Promise的长处有:
- 将回调函数的嵌套调用改为链式调用,代码好看;
- 链式调用过程中如果出错,会进入catch办法,捕捉谬误;
- Promise还提供了其余弱小的性能,比方:race、all等;
用Promise改写回调函数
在应用第三方提供的API时,如果该API是用回调函数写的,能够用Promise进行改写。
比方微信小程序发送申请的API:
wx.request({ url: '', // 申请的门路 method: "", // 申请的形式 data: {}, // 申请的数据 header: {}, // 申请头 success: (res) => { // res 响应的数据 }})
上面应用Promise改写,即在胜利回调中resolve、在失败回调中reject:
function myrequest(options) { return new Promise((resolve, reject) => { //创立Promise wx.request({ url: options.url, method: options.method || "GET", data: options.data || {}, header: options.header || {}, success: res => { resolve(res) //在胜利回调中resolve }, fail: err => { reject(err) //在失败回调中reject } }) })}
应用该自定义API:
myrequest({ url: 'xxx', header: { 'content-type': 'json' }}).then(res => { console.log(res)}).catch(err => { console.log(err)})
Promise的基本概念
Promise是ES6新增的对象,通过new来实例化,实例化时传入一个执行器函数(executor)作为参数:
// 执行器函数有两个参数:resolve、reject,它们也是函数const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作胜利 */){ resolve(value) } else { reject(error) }})
Promise的特点有:
- 对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已胜利)和rejected
(已失败)。只有异步操作的后果,能够决定以后是哪一种状态,任何其余操作都无奈扭转这个状态; - 一旦状态扭转,就不会再变,任何时候都能够失去这个后果。
Promise
对象的状态扭转,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只有这两种状况产生,状态就凝固了,不会再变了,会始终放弃这个后果,这时就称为 resolved(已定型);
Promise的三种状态
- Pending:期待;
- Fulfilled:实现,调用resolve;
- Rejected:回绝,调用reject;
从上图能够看出Promise的生命周期:
- Promise的初始状态的是Pending;
- 在创立Promise时就定义好何时resolve、何时reject;
- then办法接管resolve的后果,而catch接管reject的后果,此时Promise状态为Fulfilled或Rejected;
- then、catch办法又会返回新的Promise,从而实现链式调用;
Promise的链式调用
Promise的链式调用是如何实现的呢?先来看看Promise链式调用的个别写法:
new Promise((resolve, reject) => { setTimeout(() => { resolve() })}).then(res => { //自行处理 ... res = res + '111' //交给下一层解决 return res}).then(res => { //自行处理 ... res = res + '222' //交给下一层解决 return res})
依照上图,then办法应该返回一个Promise对象,能力持续调用then/catch办法,然而这里间接return res
为什么也行?
因为在then办法外部会主动将返回值包装成Promise,所以上述代码等价于:
new Promise((resolve, reject) => { setTimeout(() => { resolve() })}).then(res => { //自行处理 ... res = res + '111' //交给下一层解决 return Promise.resolve(res)}).then(res => { //自行处理 ... res = res + '222' //交给下一层解决 return Promise.resolve(res)})
Promise.resolve(res)
是new Promise(resolve => {resolve(res)})
的语法糖。
Promise与微工作
Promise
中的执行函数是同步进行的,然而外面可能存在着异步操作,在异步操作完结后会调用resolve
办法,或者中途遇到谬误调用reject
办法,这两者都是作为微工作进入到事件循环中。那么,Promise
为什么要引入微工作的形式来进行回调操作?
如何解决异步回调,有2种形式:
- 将回调函数放在
宏工作队列
的队尾。 - 将回调函数放到
以后宏工作中
的最初面(即作为微工作)。
- 如果采纳第一种形式,那么执行回调(resolve/reject)的机会应该是在后面所有的宏工作实现之后,假使当初的工作队列十分长,那么回调迟迟得不到执行,造成利用卡顿。
- 为了解决上述计划的问题,另外也思考到提早绑定的需要,Promise 采取第二种形式,引入微工作,即把
resolve/ reject
回调的执行放在以后宏工作的开端;
Promise的执行程序
实际上要想搞清楚Promise的执行程序,就是了解Promise是如何进入事件循环的。
前置常识:
1:每一个当下正在被执行的JS代码是放在JS的主线程中的。同步的代码会依照代码程序顺次放入主栈,而后依照放入的程序顺次执行。
2:异步的代码会被放入微工作/宏工作队列,promise属于微工作。
3:异步的代码肯定是要等到同步的代码执行完了才执行。也就是说,直到JS Stack为空,微工作队列外面的代码才会被放入主栈,而后被执行。
4:new Promise()和.then()办法属于同步代码。
5:.then(resolveCallback, rejectCallback)外面的resolveCallback, rejectCallback的执行属于异步代码,会被放入微工作队列。
6: resolve()被调用会起到两点作用:
- Promise由pending状态变为resolved;
- 遍历这个promise上所注册的所有的resolveCallback办法,顺次退出微工作队列;
7: .then()只是注册callback办法,并不会把callback办法退出微工作队列(参考下面的第6点)。
来看几个例子:
例子一
new Promise((resolve, reject)=> { console.log(4) resolve(1) Promise.resolve().then(()=>{ console.log(2) }) }).then((t)=>{console.log(t)})console.log(3)//输入为:4 3 2 1
剖析:
new Promise
的代码是同步执行的,所以其参数,即执行器函数(resolve, reject)=>{}
是同步执行的,所以打印4是立刻执行的;resolve(1)
会把外层pomise状态由pending
变成resolved
,然而因为还没执行到外层then,所以此刻最外层的promise上并没有注册任何的callback办法,也就无奈把(t)=>{console.log(t)}
退出微工作队列;Promise.resolve()
的后果曾经是resolved
了,所以外部then的回调(打印2)间接退出微工作队列;- 最初才轮到外层then的回调(打印1)退出微工作队列;
此时主栈和微工作队列:
JS Stack: [打印4,打印3]Microtask: [打印2,打印1]
例子二
new Promise((resolve, reject)=>{ Promise.resolve().then(()=>{ // cb1 resolve(1) Promise.resolve().then(()=>{console.log(2)}) // cb2 }) }).then((value)=>{console.log(value)}) // cb3console.log(3)//输入:3 1 2
剖析:
第2行then的回调(cb1)立刻退出微工作队列;
此时:JS Stack: [打印3]Microtask: [cb1]
宏工作执行完就开始执行微工作(只有一个),先执行
resolve(1)
,此时外层promise变成resolved
,所以能够执行外层then了,将外层then的回调(cb3)退出微工作队列;此时:
JS Stack: []Microtask: [cb3]
接着执行第4行,间接将
cb2
退出微工作队列;此时:
JS Stack: [cb3]Microtask: [cb2]
例子三
new Promise((resolve, reject)=>{ Promise.resolve().then(()=>{ // cb1 resolve(1); Promise.resolve().then(()=>{console.log(2)}) // cb2 }) Promise.resolve().then(()=>{console.log(4)}) // cb3}).then((t)=>{console.log(t)}) // cb4 console.log(3);//输入:3 4 1 2
剖析:
第2行和第6行then的回调(cb1、cb3)立刻退出微工作队列;
此时:JS Stack: [打印3]Microtask: [cb1, cb3]
宏工作执行完就开始执行微工作(只有一个),先执行
resolve(1)
,此时外层promise变成resolved
,所以能够执行外层then了,将外层then的回调(cb4)退出微工作队列;此时:
JS Stack: [cb3]Microtask: [cb4]
先执行主栈,打印4。接着执行第4行,间接将
cb2
退出微工作队列;此时:
JS Stack: []Microtask: [cb2]
Promise和async/await
通过以上剖析,Promise的链式调用是对于“回调天堂”的优化,然而如果链式调用太长,也不够好看。所以async/await就是进一步来优化then链的。
如果有三个步骤,每一个步骤都须要之前步骤的后果:
function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n) })}function step1(n) { console.log(`step1 with ${n}`) return takeLongTime(n)}function step2(n) { console.log(`step2 with ${n}`) return takeLongTime(n)}function step3(n) { console.log(`step3 with ${n}`) return takeLongTime(n)}
Promise链式调用会这么些:
function doIt() { console.time("doIt") const time1 = 300 step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(`result is ${result}`) console.timeEnd("doIt") })}doIt()
如果用 async/await 来实现:
async function doIt() { console.time("doIt") const time1 = 300 const time2 = await step1(time1) const time3 = await step2(time2) const result = await step3(time3) console.log(`result is ${result}`) console.timeEnd("doIt")}doIt()
后果和之前的 Promise 实现是一样的,然而代码显得很简洁,看上去跟同步代码一样。
上面来看看对于async/await的了解:
- async 用于申明一个 function 是异步的,而 await 用于期待一个异步办法执行实现;
- async 是一个修饰符,async 定义的函数会默认的返回一个Promise对象resolve的值,如果在函数中
return
一个间接量,async 会把这个间接量通过Promise.resolve()
封装成 Promise 对象; - await 期待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有非凡限定);
如果await等到的是一个 Promise 对象,它会阻塞前面的代码,等着 Promise 对象 resolve,而后失去 resolve 的值,作为 await 表达式的运算后果。
所以,能够将所有Promise的链式调用都转换成async/await的模式。
手写Promise
如果能手写出Promise
,那么对其原理的了解天然就会粗浅了。
想要手写一个 Promise,就要遵循Promise/A+ 标准,业界所有Promise
的类库都遵循这个标准。
联合Promise/A+
标准,能够剖析出Promise
的基本特征:
- promise 有三个状态:
pending
,fulfilled
,orrejected
;「标准 Promise/A+ 2.1」 new promise
时, 须要传递一个executor()
执行器,执行器立刻执行;- executor 承受两个参数,别离是
resolve
和reject
; - promise 的默认状态是
pending
; - promise 有一个
value
保留胜利状态的值,能够是undefined/thenable/promise
;「标准 Promise/A+ 1.3」 - promise 有一个
reason
保留失败状态的值;「标准 Promise/A+ 1.5」 - promise 只能从
pending
到rejected
, 或者从pending
到fulfilled
,状态一旦确认,就不会再扭转; - promise 必须有一个
then
办法,then 接管两个参数,别离是 promise 胜利的回调 onFulfilled, 和 promise 失败的回调 onRejected;「标准 Promise/A+ 2.2」 - 如果调用 then 时,promise 曾经胜利,则执行
onFulfilled
,参数是promise
的value
; - 如果调用 then 时,promise 曾经失败,那么执行
onRejected
, 参数是promise
的reason
; - 如果 then 中抛出了异样,那么就会把这个异样作为参数,传递给下一个 then 的失败的回调
onRejected
;
实现Promise如下:
// Promise的三种状态const PENDING = 'PENDING';const FULFILLED = 'FULFILLED';const REJECTED = 'REJECTED';// 自定义MyPromise类class MyPromise{ constructor(executor){ this.status = PENDING this.value = undefined this.reason = undefined // 寄存胜利的回调 this.onResolvedCallbacks = [] // 寄存失败的回调 this.onRejectedCallbacks = [] let resolve = (value) => { if(this.status === PENDING){ this.status = FULFILLED this.value = value // 顺次将对应的函数执行 this.onResolvedCallbacks.forEach(fn=>fn()) } } let reject = (reason) => { if(this.status === PENDING){ this.status = REJECTED this.reason = reason // 顺次将对应的函数执行 this.onRejectedCallbacks.forEach(fn=>fn()) } } try{ executor(resolve, reject) }catch(err){ reject(err) } } // then办法 then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reason) } // 如果promise的状态是 pending,须要将 onFulfilled 和 onRejected 函数寄存起来,期待状态确定后,再顺次将对应的函数执行 if (this.status === PENDING) { this.onResolvedCallbacks.push(() => { onFulfilled(this.value) }) this.onRejectedCallbacks.push(()=> { onRejected(this.reason) }) } }}
应用自定义的MyPromise:
const promise = new MyPromise((resolve, reject) => { setTimeout(()=>{ resolve('胜利'); },1000)}).then( (res) => { console.log('success', res) }, (err) => { console.log('faild', err) })
留神,以上只是实现了简易版的Promise
,对于链式调用、值穿透个性等还没有实现。
参考链接
- Javascript异步编程的4种办法
- 为什么Promise要引入微工作?
- Promise 对象——阮一峰
- 了解 JavaScript 的 async/await
- JS - Promise的执行程序