Promise
- 一、认识promise
- 1、什么是Promise呢?
- 2、网络请求的回调地狱
- 二、Promise的基本使用
- 1、定时器的异步事件
- 2、Promise三种状态
- 三、promise的链式调用
- 四、promise的all方法的使用
一、认识promise
Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。
1、什么是Promise呢?
(1)ES6中一个非常重要和好用的特性就是Promise。
(2)Promise是做什么的呢?
答:Promise是异步编程的一种解决方案。
(3)什么时候我们会来处理异步事件?
答:一种很常见的场景应该就是网络请求了。我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的像1+1=2一样将结果直接返回。往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦。但是,当网络请求非常复杂时,就会出现回调地狱。
用一个案例来解释说明一下
2、网络请求的回调地狱
我们来考虑下面的场景:
- 我们需要通过一个url1从服务器加载一个数据data1,data1中包含了下一个请求的url2
- 我们需要通过data1取出url2,从服务器加载数据data2,data2中包含了下一个请求的url3
- 我们需要通过data2取出url3,从服务器加载数据data3,data3中包含了下一个请求的url4
- 发送网络请求url4,获取最终的数据data4
上面的代码有什么问题呢?
正常情况下,不会有什么问题,可以正常运行并且获取我们想要的结果。但是,这样这样的代码难看而且不容易维护。如果里面包含非常复杂的操作的话,一旦有错那么排错将会非常困难,不易于维护。
在现实开发项目中,我们更加期望的是一种更加优雅、方便的方式来进行这种异步操作。本文就是使用Promise来解决这一问题。
二、Promise的基本使用
一般在有异步操作时,使用Promise对这个异步操作进行封装。
1、定时器的异步事件
我们先来看看Promise最基本的语法。这里,我们用一个定时器来模拟异步事件:
上图的代码是原始的方法,它的意思就是我们通过一个定时函数,在1s后进行相关信息的打印。在打印完后,经过1s又进行另外信息的执行,以此类推。这样不断的嵌套很多业务操作,显然结构不直观,难以维护。
将其换成Promise代码:
//参数本身就是一个函数: 参数 => 函数(resolve, reject)
//resolve, reject本身又是函数
// new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve()
// },1000)
// }).then(() => {
// console.log("Hello World");
// console.log("Hello World");
// console.log("Hello World");
// console.log("Hello World");
// console.log("Hello World");
// console.log("Hello World");
//
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve()
// },1000)
// }).then(() => {
// console.log("Hello vue");
// console.log("Hello vue");
// console.log("Hello vue");
// console.log("Hello vue");
// console.log("Hello vue");
// console.log("Hello vue");
//
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve()
// },1000)
// }).then(() => {
// console.log("Hello vuecli");
// console.log("Hello vuecli");
// console.log("Hello vuecli");
// console.log("Hello vuecli");
// console.log("Hello vuecli");
// console.log("Hello vuecli");
// })
//
// })
// })
先来看看Promise代码的结构:
对比两个代码:
首先,下面的Promise代码明显比上面原始的代码看起来还要复杂。
其次,下面的Promise代码中包含的resolve、reject、then、catch具有什么含义?
先不管它有什么含义,先来看看new promise的基本使用:
//new -> 构造函数(1.保存一些状态信息 2.执行传入的函数(回调函数))
//在执行我们传入的回调函数时,会传入两个参数:resolve, reject
//resolve, reject两个参数又是函数
new Promise((resolve, reject) => {
// 进行异步操作
setTimeout(() => {
resolve("hello vue")
},1000)
}).then((data) => {
console.log(data);
console.log(data);
console.log(data);
console.log(data);
console.log(data);
})
什么时候使用promise呢?
答:有异步操作的时候,我们就用promise包含,当异步操作成功拿到结果的时候,不要再回调函数里面去处理,通过resovle调用一下,将结果传到then里面去做相关的业务处理;当异步操作失败的时候,会拿到错误的信息,将信息拿过来,调用reject函数,然后去到catch()。
new Promise((resolve, reject) => {
// 进行异步操作
setTimeout(() => {
//成功的时候调用resolve
// resolve("hello vue")
// 失败的时候调用reject
reject("error")
},1000)
}).then((data) => {
console.log(data);
console.log(data);
console.log(data);
console.log(data);
console.log(data);
}).catch(err => {
console.log(err);
})
总结:
(1)new Promise是创建一个Promise对象
(2)小括号中((resolve, reject) => {})也很明显就是一个函数,而且这里用的是之前刚刚学习过的箭头函数。
(3)resolve, reject指的是什么?
- 我们先知道一个事实:在创建Promise时,传入的这个箭头函数是固定的(一般我们都会这样写)
- resolve和reject它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个。
- 如果是成功的,那么通常我们会调用resolve(messsage),这个时候,我们后续的then会被回调。
- 如果是失败的,那么通常我们会调用reject(error),这个时候,我们后续的catch会被回调。
2、Promise三种状态
首先, 当我们开发中有异步操作时, 就可以给异步操作包装一个Promise。异步操作之后会有三种状态,接下来介绍这三种状态。
三种状态:
- pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
- fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
- reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()
补充
promise另一种写法:
<script>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello vue')
reject('Hello vuecli')
},1000)
}).then(data => {
console.log(data);
},err => {
console.log(err);
})
</script>
then()函数里面有两个函数,第一个函数是满足时调用,也就是回调resolve时执行该函数,如果回调reject那么将会执行第二个函数。从源码我们也可以看到他的执行过程:
三、promise的链式调用
我们在看Promise的流程图时,发现无论是then还是catch都可以返回一个Promise对象。所以,我们的代码其实是可以进行链式调用的:
这里我们直接通过Promise包装了一下新的数据,将Promise对象返回了
- Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数
- Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数
第一种:
<script>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
},1000)
}).then(() => {
console.log("Hello World");
console.log("Hello World");
console.log("Hello World");
console.log("Hello World");
console.log("Hello World");
console.log("Hello World");
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
},1000)
}).then(() => {
console.log("Hello vue");
console.log("Hello vue");
console.log("Hello vue");
console.log("Hello vue");
console.log("Hello vue");
console.log("Hello vue");
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
},1000)
}).then(() => {
console.log("Hello vuecli");
console.log("Hello vuecli");
console.log("Hello vuecli");
console.log("Hello vuecli");
console.log("Hello vuecli");
console.log("Hello vuecli");
})
})
})
</script>
第二种:
网络请求:aaa -> 自己处理
// 处理:aaa111 -> 自己处理
// 处理:aaa111222 -> 自己处理
// new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve('aaa')
// },1000)
// }).then(res => {
// //1.自己处理10行代码
// console.log(res,'第一层的10行处理代码');
// //2.对结果进行第一次处理
// return new Promise((resolve, reject) => {
// resolve(res+'111')
// })
// }).then(res => {
// console.log(res,'第二层的10行处理代码');
//
// return new Promise(resolve => {
// resolve(res+'222')
// })
// }).then(res => {
// console.log(res,'第三层的10行代码');
// })
第三种是第二种的简写:
// new Promise(resolve=>resolve(结果))简写
// new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve('aaa')
// },1000)
// }).then(res => {
// //1.自己处理10行代码
// console.log(res,'第一层的10行处理代码');
// //2.对结果进行第一次处理
// return Promise.resolve(res+'111')
// }).then(res => {
// console.log(res,'第二层的10行处理代码');
//
// return Promise.resolve(res+'222')
// }).then(res => {
// console.log(res,'第三层的10行代码');
// })
第四种是第三种的简写:
- 如果我们希望数据直接包装成Promise.resolve,那么在then中可以直接返回数据
- 注意下面的代码中,将return Promise.resovle(data)改成了return data
结果依然是一样的
简化版代码:
//省略掉Promise.resolve
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
},1000)
}).then(res => {
//1.自己处理10行代码
console.log(res,'第一层的10行处理代码');
//2.对结果进行第一次处理
return res+'111'
}).then(res => {
console.log(res,'第二层的10行处理代码');
return res+'222'
}).then(res => {
console.log(res,'第三层的10行代码');
})
如果有错误的情况下:
// new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve('aaa')
// },1000)
// }).then(res => {
// //1.自己处理10行代码
// console.log(res,'第一层的10行处理代码');
// //2.对结果进行第一次处理
// return Promise.reject(err)
// }).then(res => {
// console.log(res,'第二层的10行处理代码');
//
// return res+'222'
// }).then(res => {
// console.log(res,'第三层的10行代码');
// }).catch(err=>{
// console.log(err);
// })
还可以手动抛异常
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
},1000)
}).then(res => {
//1.自己处理10行代码
console.log(res,'第一层的10行处理代码');
//2.对结果进行第一次处理
throw 'err message'
}).then(res => {
console.log(res,'第二层的10行处理代码');
return res+'222'
}).then(res => {
console.log(res,'第三层的10行代码');
}).catch(err =>{
console.log(err);
})
四、promise的all方法的使用
官方解释:Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。
如果有这么个需求,两个网页请求都完成后我们在进行下一步操作,如果用ajax实现该需求,需要使用下面的方法,用变量来记录两个请求是否执行完,只有两个变量都为true就调用函数进行相关业务处理,非常麻烦。
// 请求一:
// let isResult1 = false
// let isResult2 = false
// $ajax({
// url: '',
// success: function () {
// console.log('result1');
// isResult1 = true
// handleResult()
// }
// })
// // 请求二:
// $ajax({
// url: '',
// success: function () {
// console.log('result2');
// isResult2 = true
// handleResult()
// }
// })
//
// function handleResult() {
// if (isResult1 && isResult2) {
// //
// }
// }
因此,可以使用promise.all来实现两个异步操作都完成后才调用相关操作
Promise.all([
// new Promise((resolve, reject) => {
// $.ajax({
// url: 'url1',
// success: function (data) {
// resolve(data)
// }
// })
// }),
// new Promise((resolve, reject) => {
// $.ajax({
// url: 'url2',
// success: function (data) {
// resolve(data)
// }
// })
// })
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({name: 'victory', age: 18})
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({name: 'CEO', age: 19})
}, 1000)
})
]).then(results => {
console.log(results);
})
promise.all进一步学习可参阅官方文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all