Promise

296 阅读7分钟

什么是Promise?

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理,让开发者不用再关注于时序和底层的结果。Promise的状态具有不受外界影响和不可逆两个特点。

传统的回调式异步操作有什么缺点?(Promise是如何解决异步操作)

传统回调问题:

调用回调过早 调用回调过晚(或没有被调用) 调用回调次数过少或过多 未能传递所需的环境和参数 吞掉可能出现的错误和异常

Promise的解决办法:

1.调用回调过早  

对于Promise来说,即使是立即完成的Promise也无法被同步观察到,也就是说一个Promise调用then()的时候,即使这个Promise已经决议了,提供给then的回调也总会被异步调用。 

2.调用回调过晚(或没有被调用)  

对于一个Promise对象注册的每一个观察回调都是相对独立、互不干预的。而Promise对象调用resolve()和reject()时,每个注册的观察回调也都会被自动调度。所以这些观察回调的任意一个都无法影响或延误对其他回调的调用。 此外,关于回调未调用。正常情况下,没有任何东西可以阻止Promise向你通知它的决议,即使你的JavaScript代码报错了,一会通过异常回调来捕获到。如果Promise永远不被决议的话,Promise本身已提供了竞态的抽象机制来作为解决方案。

3.调用回调次数过少或过多

 Promise的定义方式使得它只能被决议一次。即使代码中出现多次决议,这个Promise也会接受第一次决议,并会忽略掉其他任何后续调用。所以任何通过then()注册的回调只会被调用一次。 

4.未能传递所需的环境和参数  

凡是被决议的值都会传递到观察回调中,如果没有显示的决议值也会传递一个undefined给观察回调。需要注意的是,Promise只允许传一个决议值,其他值将会被默默忽略掉。 5.吞掉可能出现的错误和异常 如果在创建Promise时,存在JavaScript代码错误,会直接导致该Promise的拒绝决议,那么你可以通过reject()来捕获异常,代码中的任何异常都不会吞掉。

Promise中的异步模式有哪些?有什么区别?

因为ES6中的Promise中只有这两个模式all和race,其他的如first、any、last等都是其他Promise库提供的。 即Promise.all()和Promise.race()的区别

all会将传入的数组中的所有promise全部决议以后,将决议值以数组的形式传入到观察回调中,任何一个promise决议为拒绝,那么就会调用拒绝回调。  

race会将传入的数组中的所有promise中第一个决议的决议值传递给观察回调,即使决议结果是拒绝。

如果向Promise.all()和Promise.race()传递空数组,运行结果会有什么不同? all会立即决议,决议结果是fullfilled,值是undefined race会永远都不决议,程序卡死…… 

如何确保一个变量是可信任的Promise(Promise.resolve方法传入不同值的不同处理有哪些)

 可以通过Promise.resolve()方法对不确定的值进行Promise化,返回一个Promise对象。 如果是一个立即值,如一个普通变量,那么该Promise会立即决议为成功。 如果是一个Promise值,那么会将该Promise直接返回赋值给这个Promise,不会有额外开销。 如果是一个类Promise值, 比如其中含有名称为then的成员变量,那么会将then展开形成一个新的Promise对象。

Promise是如何捕获异常的?与传统的try/catch相比有什么优势?

传统的try/catch捕获异常方式是无法捕获异步的异常的

        try {
            setTimeout(function(){
                undefined();//undefined不是一个方法,会抛出异常
            }, 500)
        } catch(err){
            //这里并不能捕获异常
            console.log(err);
        }


而对于Promise对象来说,构造Promise实例时的代码如果出错,则会被认为是一个拒绝的决议,并会向观察回调中传递异常信息。所以即使是一个异步的请求,Promise也是可以捕获异常的。此外,Promise还可以通过catch回调来捕获回调中的异常。

题目一

        new Promise(function(resolve){
            console.log(1)
            setTimeout(function(){
                console.log(2)
                resolve();
                console.log(3)
            })
        }).then(function(){
            throw new Error("fail")
        }).then(function(){
            console.log(4)
        }).then(function(){
            console.log(5)
        }).then(function(){
            console.log(6)
        }).then(function(){
            console.log(7)
        })
        console.log(8)


题目二

        new Promise(function(resolve){
            console.log(1)
            setTimeout(function(){
                console.log(2)
                resolve();
                console.log(3)
            })
        }).then(function(){
            throw new Error("fail")
        }).catch(e => {
            console.log('运行过程中出现错误');
        }).then(function(){
            console.log(4)
        }).then(function(){
            console.log(5)
        }).then(function(){
            console.log(6)
        }).then(function(){
            console.log(7)
        })
        console.log(8)



解释:上面两道题目主要考察了Promise的链式调用以及异常处理 ,Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的, promise 的 then 方法里面可以继续返回一个新的 promise 对象 下一个 then 方法的参数是上一个 promise 对象的 resolve 参数 catch 方法的参数是其之前某个 promise 对象的 rejecte 参数 一旦某个 then 方法里面的 promise 状态改变为了 rejected,则promise 方法连会跳过后面的 then 直接执行 catch catch 方法里面依旧可以返回一个新的 promise 对象

题目三

                const promise = new Promise((resolve, reject) => {
			setTimeout(() => {
				resolve('success')
			}, 1000)
		})
		console.log(promise)
		promise
			.then(() => {
		    	console.log(promise)
		    	throw new Error('error!!!')
		  	})
		  	.catch(err => {
		    	console.log(promise)
		    })


注意:这道题目考察了promise 有 3 种状态:pending、fulfilled 和 rejected。状态改变只能是 pending->fulfilled 或者 pending->rejected,状态一旦改变则不能再变。

题目四

                const promise = new Promise((resolve, reject) => {
			  resolve('success1')
			  reject('error')
			  resolve('success2')
		})

		promise
			.then((res) => {
			    console.log('then: ', res)
			})
			.catch((err) => {
			    console.log('catch: ', err)
			})


注意:这道题目考察了构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用,呼应了题目3结论:promise 状态一旦改变则不能再变。

题目五

                    Promise.resolve(1)
   			.then((res) => {
		    	console.log(res)
		    	return 2
			})
		    .catch((err) => {
		    	return 3
		    })
		    .then((res) => {
		    	console.log(res)
		    })


注意:这道题目考察了promise 可以链式调用。提起链式调用我们通常会想到通过 return this 实现,不过 Promise 并不是这样实现的。promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用。


题目六

                const promise = new Promise((resolve, reject) => {
			setTimeout(() => {
				console.log('once')
				resolve('success')
			}, 1000)
		})

		const start = Date.now()
		promise.then((res) => {
			console.log(res, Date.now() - start)
		})
		promise.then((res) => {
			console.log(res, Date.now() - start)
		})


注意:这道题目考察了promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。

题目七

                    Promise.resolve()
			.then(() => {
				return new Error('error!!!')
			})
			.then((res) => {
				console.log('then: ', res)
			})
			.catch((err) => {
				console.log('catch: ', err)
			})


注意:这道题目考察了.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获,需要改成其中一种:

  1. return Promise.reject(new Error('error!!!'))
  2. throw new Error('error!!!')

因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))。

题目八

                         const promise = Promise.resolve()
		  		.then(() => {
		    		return promise
		  		})
		          promise.catch(console.error)


注意:解释:.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。类似于:

process.nextTick(function tick () {
  console.log('tick')
  process.nextTick(tick)
})

用Promise封装ajax

        const Ajax = ({
		method = "GET",
		url = "/",
		data = {},
		async = true
	}) => {
		return new Promise((resolve,reject) => {
			let xhr = new XMLHttpRequest();
			xhr.onreadystatechange = () => {
				if(xhr.readyState === 4 && xhr.status === 200) {
					let res = JSON.parse(xhr.responseText)
					resolve(res)
				}
			}
			xhr.open(method,url,async)
			if(method === "GET") {
				xhr.send()
			}else if(method === "POST") {
				xhr.send(data)
			}
		})
	}

	Ajax({
		"url": "https://api.github.com/repos/vmg/redcarpet/issues?state=closed"
	})
	.then((data) => {
		console.log(data)
	})
	</script>



总结

Promise是一个不错异步操作解决方案,他解决了传统的通过回调和事件来解决异步操作的诸多问题,如“竞争”,回调信任度低的问题。ES6中也提供了标准的Promise供大家使用。