谈谈Promise

说到Promise,总是不可避免的会与一个词联系起来----异步。是的,Promise生来最大的作用便是为了解决回调地狱的问题。

开发过程中,为了使异步操作按顺序进行,或者说,下一个异步操作需要上一个异步操作的返回结果。在这种情况下,我们原先的做法是使用回调函数。下列代码就是典型的使用回调函数的场景,我们需要使三个异步函数(假设它们是)按照我们希望的顺序执行。

function async1(result, callback){
	callback(result);
}

function async2(result, callback){
	callback(result);
}

function async3(result, callback){
	callback(result);
}

async1('第一个异步操作开始', (result)=>{
	console.log(result);
	async2('第二个异步操作开始', (result)=>{
		console.log(result);
		async3('第三个异步操作开始', (result)=>{
			console.log(result);
		});
	});
});
/*
第一个异步操作开始
第二个异步操作开始
第三个异步操作开始
*/

在仅有三个异步函数的情况下,代码看起来还比较清晰有条理。不过可以想见随着异步函数的增多,嵌套的层数也在不停的增加,很快那部分代码就会变得杂乱无章,也就是陷入了所谓的 “回调地狱”

既然我们说了Promise是生来解决这种情况的,那么使用了Promise之后的代码会是什么样的呢?

function async1(result){
	return new Promise((resolve, reject)=>{
		resolve(result);
	});
}

function async2(result){
	return new Promise((resolve, reject)=>{
		resolve(result);
	});	
}

function async3(result){
	return new Promise((resolve, reject)=>{
		resolve(result);
	});
}

async1('开始第一个异步操作').then((result)=>{
	console.log(result);
	return async2('开始第二个异步操作');
})
.then((result)=>{
	console.log(result);
	return async3('开始第三个异步操作');
})
.then((result)=>{
	console.log(result);
})
.catch((err)=>{
	console.log('出错了');
});

/*
第一个异步操作开始
第二个异步操作开始
第三个异步操作开始
*/

暂且先不论Promise的语法,使用了Promise之后最明显的变化就是简化了层层嵌套回调的写法,使之变成了链式调用。这种写法不论是从代码的逻辑上还是显示上都提升了可读性。

既然Promise这么好,那该怎么用呢?我们从上面的代码开始分析。

先来看看async1函数。

function async1(result){
	return new Promise((resolve, reject)=>{
		resolve(result);
	});
}

在这个函数里返回了一个新创建的Promise对象,这个对象接受一个函数作为参数。这个函数就是我们希望进行的一些操作,只不过我们把它封装在Promise对象内。这个传给Promise对象的函数本身还接收两个参数resolve和reject

resolve和reject参数实际上是两个函数。通过调用resolve或reject方法可以分别将Promise对象的状态修改为fulfilled或rejected。在没有调用以上两个方法中任何一个的情况下,Promise对象的状态将会一直是Pending

说到这里,就必须说说Promise的状态了。根据Promises/A+的标准,Promise共有3种状态,分别是:

  • Pending
  • fulfilled
  • rejected

这3种不同状态下的Promise对象也具有不一样的特点:

扫描二维码关注公众号,回复: 4800947 查看本文章
  1. 在Pending状态下,Promise对象可能会转变成fulfilled或rejected状态。(只能转变成其中一种)
  2. 在fullfilled状态下,Promise不能再转变状态。同时一定要有一个不会变的值。
  3. 在rejected状态下,Promise不能再转变状态。同时一定要有一个不会变的原因。

接下来我们再看看Promise对象的then方法。

async1('开始第一个异步操作').then((result)=>{
	console.log(result);
	return async2('开始第二个异步操作');
})
.then((result)=>{
	console.log(result);
	return async3('开始第三个异步操作');
})
.then((result)=>{
	console.log(result);
})
.catch((err)=>{
	console.log('出错了');
});

Promise对象的then方法可以接受两个函数作为参数(这两个函数是可选的),在标准中两个参数分别被称作onFulfilled 和 onRejected。实际上这两个参数可以看成是回调函数,onFulfilled是在Promise对象的状态变为fulfilled后被调用,而onRejected则是在Promise对象的状态变为rejected之后被调用。

需要注意的是,Promise并没有解决掉需要使用回调函数处理异步函数的问题,Promise只是让这个过程从逻辑和代码可读性上都大幅简化。

then方法除了为Promise对象添加了在不同状态下执行的回调函数以外,它还会返回一个新的Promise对象。当然这从链式调用的特征中也可以看出来,如同jQuery的链式调用一样。只不过jQuery中的链式调用是返回的自身,而Promise的then方法的链式调用是返回一个新的Promise对象。

我们注意到,在上面的例子中的onFulfilled(也就是then方法里传入的那个函数)是要接受一个参数的,然而这个参数是谁传的呢?答案就是resolve函数传的。应当注意到传递给async1函数的参数被传递给了resolve函数,而resolve函数又把该参数传给了onFulfilled

似乎我们上面的代码中并没有为then方法传入onRejected方法。确实如此,不过我们通过最后的catch方法为所有的then方法都指定了onRejected。也就是说,链式调用中任意一个then方法中返回的Promise对象变为了rejected状态,都会调用传给catch方法的回调函数。

在上面代码的链式操作中,我们在onFulfilled里返回了一个Promise,如下代码所示。

async1('开始第一个异步操作').then((result)=>{
	console.log(result);
	return async2('开始第二个异步操作');
})

我在开始接触Promise的时候,看到这样的写法心里是有许多问号的。第一个就是既然如我们上面所说的,then方法会返回一个新的Promise对象,那在 onFulfilled 里再返回一个Promise是什么意思?第二个问题就是,在前一个then方法里获取到的前一个异步操作的数据是怎么传给后一个then方法的?

首先,如果我们在onFulfilled里面返回了一个Promise对象,那么then方法返回的那个Promise对象的状态将会与该Promise对象的状态联动。换句话说,onFulfilled里返回的Promise对象的状态变为什么,then方法返回的Promise对象的状态就会变成什么。

而后一个then方法获得的数据则是前一个then方法中的onFulfilled里返回的Promise对象里传给resolve的参数。也就是下面代码中传给resolve的那个result参数

function async2(result){
	return new Promise((resolve, reject)=>{
		resolve(result);
	});	
}

不单只是Promise对象,实际上在onFulfilled里面可以直接返回数据。我把代码改成如下。

function async1(result){
	return new Promise((resolve, reject)=>{
		resolve(result);
	});
}

async1('开始第一个异步操作').then((result)=>{
	console.log(result);
	return '开始第二个异步操作';
})
.then((result)=>{
	console.log(result);
	return '开始第三个异步操作';
})
.then((result)=>{
	console.log(result);
})
.catch((err)=>{
	console.log('出错了');
});

/*
第一个异步操作开始
第二个异步操作开始
第三个异步操作开始
*/

这一次在onFulfilled中不返回Promise对象而是直接返回数据,执行结果也与之前返回Promise对象的写法一致。若是直接返回数据,该数据就会直接传递给下一个then方法的onFulfilled

可能有人会好奇如果什么都不返回会怎么样。如果什么都不返回,那么后一个then方法中的onFulfilled接收到的将会是undefined

除了以上那些。Promise这个构造函数本身也有一些强大的方法。

首先说说all方法

function async1(result){
	return new Promise((resolve, reject)=>{
		resolve(result);
	});
}

Promise.all([async1('数据1'), async1('数据2'), async1('数据3')]).then((result)=>{
	console.log(result);
});

// [ '数据1', '数据2', '数据3' ]

all方法可以并行多个异步操作,然后这些异步操作的结果将会统一存在一个数组里被传递到then方法的onFulfilled里。

接下来说说race方法,all方法是要等到所有数组中所有Promise对象都改变了状态才进入then方法。而race方法则是按数组中改变状态最快的那个Promise对象的状态来进入race方法。

function async1(result){
	return new Promise((resolve, reject)=>{
		resolve(result);
	});
}

function async2(result){
	return new Promise((resolve, reject)=>{
		setTimeout(()=>{
			resolve(result);
		}, 1)
	});
}

Promise.race([async1('数据1'), async2('数据2')]).then((result)=>{
	console.log(result);
});
//数据1

由于async2比async1晚了1ms执行resolve函数,所以最终的输出结果是数据1。

猜你喜欢

转载自blog.csdn.net/hjc256/article/details/84635184