Promise解析分享

897 阅读9分钟

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()方法的参数分成四种情况。

  1. 参数是一个 Promise 实例,如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
  2. 参数是一个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'}
});

`

  1. 参数就是一个普通对象,或者其他数据。如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,并把这个参数作为fulfilled状态的返回值。
  2. 啥参数都不传,如果什么参数都么有传,其实就是和第三种情况一样,区别就是它么有传递数据出来。

手写Promise

手写Promise现在网上的教程和案例很多,都是可以值得去学习的,手写Promise在我们求职面试过程中,也是经常遇到面试题,所以这玩意是有必要去学习和掌握的。这里分享一个我学习过程,像这种手写案例你不会的,么得关系,网上找个案例抄,抄一遍你就有概念,抄两遍你就能理解,抄三遍你就能深入了解和学习到人家的思想了,所以,话说到这个份上,要不要掌握和学习就看你自己了,下面我们开始实现我们自己的myPromise。

  1. 第一步,我们的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)

`

mp1.jpeg

  1. 第二步,添加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实例值,是和数组参数的序列一致的

`

static all(arr) { // static关键字 就是申明静态方法的属性
let count = 0
let results = []
return new myPromise(function(resolve, reject) {
	function handle(mp, index) {						
	if (mp instanceof myPromise) {						
	  mp.then(res => {
	  results[index] = res // 根据索引存储
	  count++
	if (count === arr.length) {
	  resolve(results)
	}
	}, err => {
	  reject(err)
})
} else {
        results[index] = mp
	count++
}			
}						
for (let i = 0; i < arr.length; i++) {
	let mp = arr[i]
	handle(mp, i)
}
})
}

` 上面这个方法就是all方法实现的代码了,实现到这个步骤,我们主要实现了myPromise这个类,以及它的两个重要方法,then和all方法。promise的其他方法,就不在赘述了,大家可以自己观察这个方法的功能,看看怎样去实现它们。 今天的Promise解析分享到这里结束。本文重点参考了阮一峰巨佬的 ECMAscript6入门