Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。Promise对象有两个特点,(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。下面是我列的几个Promise的api,也是我们实际开发当中应用频率最高的api,因此结合个人的使用,对这几个api做个分享,以及如何实现一个手写myPromise
Promise.prototype.then()
Promise.prototype.finally()
Promise.all()
Promise.resolve()
手写Promise
then
then
方法是Promise实例具有的方法,它的作用是为 Promise 实例添加状态改变时的回调函数,then方法的第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数,它们都是可选的。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then
方法后面再调用另一个then
方法。
`
let p1 = new Promise(function(resolve, reject){ resolve({id: 1, name: 'p1'})})p1.then(res => { console.log(res) //输出 {id: 1, name: 'p1'} return {id: 2, name: 'p2'}}).then(res => { // 此处链式调用,上面的return返回的是新的promise实例 console.log(res) // 输出 {id: 2, name: 'p2'}})
`
finally
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作,意思就是状态不管是fulfilled还是rejected,它都必定执行。 finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
`
let p1 = new Promise(function(resolve, reject){ resolve({id: 1, name: 'p1'})})p1.then(res => { console.log(res)}).finally(() => { console.log('上面执行完,我也必定执行')})
`
finally最典型的应用场景就是我们的ajax请求的loading关闭,通过这个函数,我们就不需要在ajax请求成功或失败里都去写loading=false了。
all
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
`
let p1 = new Promise(function(resolve){ resolve({id: 1, name: 'p1'})})let p2 = new Promise(function(resolve){ resolve({id: 2, name: 'p2'})})let p3 = {id: 3, name: 'p3'}Promise.all([p1, p2, p3]).then(res => { console.log(res) // 输出 [{id: 1, name: 'p1'}, {id: 2, name: 'p2'}, {id: 3, name: 'p3'}]})
`
Promise.all的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,Promise.all的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给Promise.all的回调函数,如上案例。
(2)只要p1、p2、p3之中有一个被rejected,Promise.all的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给Promise.all的回调函数。
`
let p1 ...let p2 = new Promise(function(resolve){ throw new Error('报个错') resolve({id: 2. name: 'p2'})})let p3 ...Promise.all([p1, p2, p3]).then(res => { // 这里则不在执行}, err => { // 捕获得错误这里触发 console.log(err) // 输出 error 报个错})
` 这里有个注意事项:p2抛出来的错误,要是被自己这个实例的catch捕获了,那么Promise.all的catch则不会执行,还是会执行fulfilled的状态结果,只是p2这里的返回值会变成undefined,看下面案例
`
let p1 ...let p2 = new Promise(function(resolve){ throw new Error('报个错') resolve({id: 2. name: 'p2'})}).then(res => res).catch(err => { console.log(err) // 这里输出错误})let p3 ...Promise.all([p1, p2, p3]).then(res => { console.log(res) // 输出 [{id: 1, name: 'p1'}, undefined, {id: 3, name: 'p3'}]}, err => { // 这里则不在触发了})
` 在上面得案例中,p3是个普通对象,把普通对象作为Promise.all数组里的数据传进去,则Promise则会把它处理成Promise的fulfilled状态返回值。
Promise.resolve
有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。在我们上面案例中的p3,在内部转换机制过程中,其实就是调用了这个Promise.resolve()
。 Promise.resolve()方法的参数分成四种情况。
参数是一个 Promise 实例,如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
参数是一个thenable
对象,所谓thenable对象,就是指普通对象里面有一个then方法,Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。
`
let thenable = { // 这个就是thenable对象 then: function(resolve, reject){ resolve({id: 1, name: 'thenable'}) }}let p1 = Promise.resolve(thenable);p1.then(function (value) { console.log(value); // {id: 1, name: 'thenable'}});
`
参数就是一个普通对象,或者其他数据。如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,并把这个参数作为fulfilled状态的返回值。
啥参数都不传,如果什么参数都么有传,其实就是和第三种情况一样,区别就是它么有传递数据出来。
手写Promise
手写Promise现在网上的教程和案例很多,都是可以值得去学习的,手写Promise在我们求职面试过程中,也是经常遇到面试题,所以这玩意是有必要去学习和掌握的。这里分享一个我学习过程,像这种手写案例你不会的,么得关系,网上找个案例抄,抄一遍你就有概念,抄两遍你就能理解,抄三遍你就能深入了解和学习到人家的思想了,所以,话说到这个份上,要不要掌握和学习就看你自己了,下面我们开始实现我们自己的myPromise。
第一步,我们的Promises是个构造函数,并且promise有三个状态:成功fulfilled
,失败rejected
,进行中pending
, 并且状态间的变化,只能从进行中到成功或者失败,一旦进入这两个状态中的一个,则状态就定型,不在改变。这个构造函数有一个回调函数,这个函数同样还有两个函数参数,一个是接收成功状态的函数,一个失败的,我们代码如下。
`
class myPromise{constructor(callback) { this.init() callback(this.resolve.bind(this), this.reject.bind(this))} init() { this.state = 'pending' this.value = null }resolve(data) { if (this.state === 'pending') { this.state = 'fulfilled' this.value = data }}reject(err) { if (this.state === 'pending') { this.state = 'rejected' this.value = err } }}let mp1 = new myPromise(function(resolve, reject){ resolve({tip: '测试看看状态变化'}) reject({tip: '我会覆盖上面的状态不?'})})console.log(mp1)
`
第二步,添加then方法,then方法是有两个函数参数的,一个是成功的回调,一个是失败的回调,下面我们继续给它添加方法:
`
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val; onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err} const self = this return new myPromise(function(resolve, reject){ if (self.state === 'fulfilled') { let value = onFulfilled(self.value) resolve(value) } if (self.state === 'rejected') { let err = onRejected(self.value) reject(err) } }) }mp1.then(res => { console.log(res) // {tip: '测试看看状态变化'} return {id: 2, tip: '试试看可以链式调用不'}}).then(res => { console.log(res) // {id: 2, tip: '试试看可以链式调用不'}})
` 我们的myPromise,实现到这一步,其实基本形态都已经出来了,但是上面的代码还是有问题的,
问题1:我们的错误它不晓得捕获的;
问题2:我们的then方法是一个微任务的异步函数,现在我们的then就是个普通的同步的函数而已;
问题3:我们的then方法是可以在接收异步返回值链式调用的,现在的then只能进行同步函数链式调用。
接下来我们分步骤解决这几个问题:错误捕获,在我们js中用啥处理,不就是try{}catch(){}
,那安排上吧:变动在于constructor
构造函数里的代码块,拿try{}catch(){}
包裹:
`
constructor(callback) { try{ this.init() callback(this.resolve.bind(this), this.reject.bind(this)) } catch(err) { this.reject(err) }}
` 第二个问题是说我们的then方法是个微任务的异步函数,那我们就把异步也加上吧,还有第三个问题是说myPromise接收异步的返回结果不能链式调用的问题,这里我们就一并处理:
`
class myPromise{constructor(callback) { try{ this.init() callback(this.resolve.bind(this), this.reject.bind(this))} catch(err) { this.reject(err) }}init() { this.state = 'pending' this.value = null this.resolveList = [] this.rejectList = []}resolve(data) { if (this.state === 'pending') { this.state = 'fulfilled' this.value = data while(this.resolveList.length) { this.resolveList.shift()(this.value) } }}reject(err) { if (this.state === 'pending') { this.state = 'rejected' this.value = err while(this.rejectList.length) { this.rejectList.shift()(this.value) } }}then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val; onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err} const self = this return new myPromise(function(resolve, reject){ function microtask (fn) { // 定义异步处理函数,请注意这个fn,这个fn其实就是then方法的两个回调函数 queueMicrotask(() => { // 这是es6新的api,微任务的一个方法 try{ let result = fn(self.value) if (result === self) { throw new Error('不能调用自己') } if (result instanceof myPromise) { result(resolve, reject) } else { resolve(result) } } catch(err) { reject(err) } }) } if (self.state === 'fulfilled') { microtask(onFulfilled) } if (self.state === 'rejected') { microtask(onRejected) } if (self.state === 'pending') { // 这里是处理myPromise处理异步返回值 // 之所以异步未能正常处理,就是因为myPromise的状态一直是pending self.resolveList.push(microtask.bind(self, onFulfilled)) self.rejectList.push(microtask.bind(self, onRejected)) } })}}let mp1 = new myPromise(function(resolve, reject){setTimeout(() => { resolve({tip: '测试看看状态变化'}) reject({tip: '我会覆盖上面的状态不?'})}, 1000) })mp1.then(res => { console.log(res) // 一秒后输出 {tip: '测试看看状态变化'}return {id: 2, tip: '继续then试试看'}}).then(res => { console.log(res) // 上一个then输出完以后就到它{id: 2, tip: '继续then试试看'}})
`
实现到这里,我们这个myPromise的then
方法已经是完整的了,大家可以自己在浏览器里试试输出打印,这里就不在演示,下面在继续给它增加个all
方法。
all
方法是Promise里的一个静态方法,静态方法就是由Promise自己调用的,不需要实例话,注意,目前es6支持静态方法,暂时还么有支持静态属性哦。好了下面开始增加all方法,all方法的功能是,接收一个数组参数,参数数组是个promise实例的集合,all方法同样返回一个新的promise实例,这个新实例的fulfilled状态的值就是数组参数所有promise实例状态为fulfilled的值,注意:all方法这个返回的promise实例值,是和数组参数的序列一致的:
`
let p1 = new Promise(function(resolve, reject){ resolve({id: 1, name: 'p1'})})p1.then(res => { console.log(res)}).finally(() => { console.log('上面执行完,我也必定执行')})0
` 上面这个方法就是all方法实现的代码了,实现到这个步骤,我们主要实现了myPromise这个类,以及它的两个重要方法,then和all方法。promise的其他方法,就不在赘述了,大家可以自己观察这个方法的功能,看看怎样去实现它们。 今天的Promise解析分享到这里结束。本文重点参考了阮一峰巨佬的 ECMAscript6入门
原文:https://juejin.cn/post/7099746789766463518