Promise学习
插图来自 coderwhy,微信 coderwhy001
一、什么是Promise
ES6中一个非常重要的特性就是Promise,首先,Promise是做什么的呢?
Promise是异步编程的一种解决方案,那为什么需要这种异步编程的解决方案呢,一般又是在上面时候会有异步编程呢?很常见的场景就是网络请求了,有时我们需要在网络请求结束后对结果进行一些清洗或者是一些其它操作,这时候就需要进行异步的编程,就是给ajax方法传一个回调函数,然后让他在接收到数据后进行回调。不过,网络请求非常复杂时,就会出现回调地狱。
二、网络请求的回调地狱
看起来非常夸张,可能现在看起来好像还行,还是一眼能看的清楚,不过,如果每次请求到数据后都需要进行一些操作呢,就像是需要数据清洗,将我们需要的数据存储好,然后再拿出第二次的url呢。如果每次这些操作都需要100行代码,那看起来就乱套了,这就是地狱。所以我们肯定希望有一种异步编程的方案,能够优雅的解决这种问题,这就需要使用到promise了。
三、Promise的基本使用
下面,通过setTimeout来模拟网络请求,演示一下Promise的基本使用。
创建Promise的时候传入一个箭头函数,这个箭头函数的两个参数resolve和reject也是函数。
new Promise((resolve,reject) => {
setTimeout(() => {
//1.网络请求成功的时候调用resolve,会下去执行then方法
//进行接下来的操作,resolve方法中可以传入数据
// resolve("数据请求成功");
//2.网络请求失败的时候调用reject,会下去执行catch方法
//捕获抛出的异常,reject方法中可以传入数据
reject("404");
console.log(111);//会执行
},1000)
}).then(res => {
console.log(res+'111');
}).catch(err => {
alert(err);
})
这看起来就像是try{}catch(Exception e){}中又添加了一个成功之后执行的then一样,不过和try/catch不同的一点是,Promise的options那个方法是一定会走到底的,就算是调用了reject(),它下面的代码还是会执行。所以resolve和reject只是模拟了成功和失败这两种情况下的操作,它们本质是还是一个方法。只是让我们来进行判断,执行到什么时候叫做成功,你感觉成功了就调用resolve方法去执行then.
四、Promise的三种状态和另外的处理方式
由这个图例可以看出,Promise就是将异步操作进行了包装,wrapped into,封装成
上图是Promise的执行过程
- pending:等待状态,比如正在进行网络请求,或者定时器没到时间
- fulfilled:满足状态,当我们主动回调了resolve时,就处于该状态,然后它会回调then()
- reject:拒绝状态,当我们主动回调了reject时,就处于该状态,然后它会回调catch()
除了上面介绍过的基本用法之外,Promise还能这么写
new Promise((resolve,reject) => {
setTimeout(() => {
// resolve("已经成功拿到数据了")
reject("本次请求失败了")
},2000)
}).then(res => {
console.log(res);
},err => {
console.log(err);
})
这下子就省略了catch,只留下了一个then
五、Promise的链式调用
-
如果需要连续的进行多次网络请求,或是其它异步操作,可以这样
new Promise((resolve,reject) => { setTimeout(() => { resolve("000"); },1000) }).then(res => { //第一次对结果进行处理 console.log("第一次对结果进行处理"); //开始第二次的请求 return new Promise((resolve,reject) => { setTimeout(() => { resolve(res+"111"); },1000) }) }).then(res => { //又是一些操作 console.log("对第二次结果进行处理"); //开始第三次请求 return new Promise((resolve,reject) => { setTimeout(() => { resolve(res+"000") },2000); }).then(res => { console.log("对第三次结果进行处理"); }) })
-
如果不需要进行连续的异步操作,而是想把处理结果的代码给拆分开,方便维护和管理的话,可以这样
new Promise((resolve,reject) => { setTimeout(() => { resolve("000"); },1000) }).then(res => { console.log("第一次对结果进行处理"); return new Promise((resolve,reject) => { resolve(res+"111"); }) }).then(res => { console.log("对第二次结果进行处理"); return new Promise((resolve,reject) => { resolve(res+"000") }).then(res => { console.log("对第三次结果进行处理"); console.log(res); })
不过,都不需要进行异步操作了,只需要调用resolve传入下一步就可以了,还要每次手动new 一个promise,显得有点麻烦。所以,promise可以这样简写,它会帮我们创建一个Promise然后传入下一级。
new Promise((resolve,reject) => { setTimeout(() => { resolve("000"); },1000) }).then(res => { console.log("第一次对结果进行处理"); return Promise.resolve(res+"111"); }).then(res => { console.log("对第二次结果进行处理"); return Promise.resolve(res+"111"); }).then(res => { console.log("对第三次结果进行处理"); console.log(res); })
还能进一步的简化,连Promise.resolve都不用写了,直接返回一个结果,它会自动创建一个Promise然后调用resolve方法。
new Promise((resolve,reject) => { setTimeout(() => { resolve("000"); },1000) }).then(res => { console.log("第一次对结果进行处理"); return res+"111"; }).then(res => { console.log("对第二次结果进行处理"); return res+"111"; }).then(res => { console.log("对第三次结果进行处理"); console.log(res); })
千万记住,这上面这些简写方式只是针对于往下一步传入结果的用法。如果想要发送再一次的网络请求或者进行新的异步操作,那还是得自己创建一个Promise.
六、Promise的all方法
有的时候,我们一个业务需要多个网络请求都到了之后才能进行,以前的话,我们得这么写。
// 请求一:
let isResult1 = false
let isResult2 = false
$ajax({
url: '',
success: function () {
console.log('结果1');
isResult1 = true
handleResult()
}
})
// 请求二:
$ajax({
url: '',
success: function () {
console.log('结果2');
isResult2 = true
handleResult()
}
})
function handleResult() {
if (isResult1 && isResult2) {
//
}
}
现在有了Promise,就 可以直接调用它的all方法,别说是两个请求了,就是十个八个都不再话下了,再也不用定义一堆表示状态的变量了。
Promise.all([
new Promise((resolve,reject) => {
setTimeout(() => {
resolve("hello");
},5000)
}),
new Promise((resolve,reject) => {
setTimeout(() => {
resolve("world");
},1000)
})
]).then(results => {
console.log(results);
})
上面就模拟了,几个网络请求需要都到了才进行操作的场景。
这里假设有两个网络请求,然后一个需要5秒,一个需要1秒,最终会在5秒后对结果进行一个打印,而且最好的一点是,这个结果数组中,并不是谁的结果先来就排在前面,而是根据在all中那个iterator可迭代对象中请求的顺序来排队的。
所以这里的results是这样的 ["hello","world"]