ES6之-优雅的Promise
文章目录
前言
提示:再讲正篇之前我们可以简单的了解一下JavaScript的异步编程的历程,这将使你对Promise的理解更深一步,如果足够了解请前往目录正篇
🐖:想要学习Vue,强烈推荐去BiliBili大学学习coderwhy老师的视频课
异步编程
Promise又叫期约
,我们在讲解Promise之前,希望大家先了解一个概念:异步编程
特别是在JavaScript这种单线程事件循环模型
中,同步操作与异步操作更是代码所要依赖的核心机制。异步行为是为了优化因计算机量大而时间长的操作。如果在等待其他操作完成的同时,即使运行其他指令,系统也能保持稳定,那么这样就是务实的
顾名思义:
- 同步:内存中顺序执行的处理指令
- 异步:类似于系统中断,异步操作经常是必要的,因为强制进程等待一个长时间的操作(同步操作则必须要等)是绝对不可取的
以往的异步编程模式
在早期的JavaScript中,只支持定义回调函数来表明异步操作完成,串联多个异步操作是一个常见的问题,通常需要深度嵌套的回调函数(**回调地狱**
)
setTimeout
可以定义一个在指定时间之后会被调度执行的回调函数,这是它被称作异步函数
的原因
function invoke(){
setTimeout( () => {
console.log('函数将在两秒后执行');
},2000)
}
invoke();
所以同样的我们可以用setTimeout
来进行很多操作:
- 设置一个异步返回值
- 失败处理
- 嵌套异步回调
但是写出层层嵌套的回调函数,如果嵌套过多,会极大影响代码可读性和逻辑,这种情况也被称作回调地狱。
我们当然希望以一种更优雅的方式去处理异步编程
那就是 Promise
总结:
简单来说Promise就是对异步编程进行优雅封装
正篇:
综上:
Promise就是异步编程的解决方案 |
我们在处理复杂的网络请求或者其他的一些异步操作的时候,同步会导致进程的阻塞,等待进程导致页面无法显示这就太可笑了
并且早期的异步编程代码冗杂可观性极差,这就用到了Promise
优雅的Promise |
Promise本身是一个对象,是一个构造函数,它需要传入一个参数,并且这个参数必须是函数
new Promise(参数)
new Promise(() => {
//用箭头函数的写法传入一个函数
})
传入的这个函数本身包含两个参数resolve
和reject
new Promise((resolve,reject) => {
})
注意:resolve
和reject
本身又是一个函数
因为传入的是函数,所以我们可以直接简单的写入一个定时器函数
new Promise((resolve,reject) => {
setTimeout( ()=> {
})
})
Promise的三种状态
- 待定(pending)
- 兑现(fulfilled,有时也称为解决,resolve)
- 拒绝(rejected)
待定(pending)就是期约最初始的状态
- 成功就将待定转换为解决状态
- 失败就将待定转换为拒绝状态
- 注意一旦转换为两种状态,就再也无法改变,无论落定为哪种状态都是
不可逆
的
有两种过度:
pending -> fulfilled
或者是pending -> rejected
错误机制:
如果错误已经捕获了,那么错误不会继续传递下去 如果错误没有被捕获,那么错误会隐式传递下去,直到有错误处理函数来捕获这个错误
resolve.then、reject.catch
- then方法接受一个参数-resolve返回的数据(正常时)
- catch方法接收一个参数-reject返回的信息(抛出异常)
我们只需要记住
- resolved(解决)时会执行then(继续)
- rejected(拒绝)时会执行catch(捕获异常)
简单的实例:
new Promise((resolve,reject) => {
setTimeout(()=> {
//resolve('success')
reject('Error Data')
},1000)
}).then((data)=>{
console.log(data);
}).catch((data)=> {
console.log(data);
})
源码分析:
Promise
我们ctrl点击进入Promise
学过Java的不难看出Promise
其实是一个类,用来实现 PromiseConstructor
这个接口,new Promise
其实是new一个对象实例调用构造函数
interface PromiseConstructor
executor是exe的全名:译为可执行,里面定义了resolve和reject两个函数
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
then函数的另外一种使用格式
我们来看源码定义Promise的接口
注意这个then函数,我们把它单独拿出来分析
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
这一段和最后一小段是泛型,我们先不管它
then<TResult1 = T, TResult2 = never>
有没有看到后面一大段内容被一个逗号分隔开,这其实本身又是两个函数
第一个函数会在onfulfilled(解决)时候执行
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
第二个函数会在onrejected(拒绝)时候执行
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
| 表示并集
,所以这个可以传undefined和null
所以可以写作下面的样式
new Promise((resolve,reject) => {
setTimeout(()=> {
//resolve('success')
reject('Error Data')
},1000)
}).then(函数1,函数2)
因为本质就是resolve执行then和
reject执行catch
所以分析源码我们就可以把.catch.then方法全部写到.then里,
new Promise((resolve,reject) => {
setTimeout(()=> {
//resolve('success')
reject('Error Data')
},1000)
}).then(data=>{
console.log(data)//resolve时执行
},err=>{
console.log(err)//reject时执行
})
Promise的链式编程思想
为了方便大家理解,我们用简单的数据或者输出代替复杂的网络请求
假如有一个需求:
我们需要进行多次网络请求时,并对请求回来的数据进行大量代码处理
不使用Promise |
new Promise((resolve,reject) => {
setTimeout(()=> {
console.log('这里是第一次网络请求回来的数据处理代码');
console.log('大约有100行');
setTimeout(()=> {
console.log('这里是第二次网络请求回来的数据处理代码');
console.log('大约有100行');
setTimeout(() => {
console.log('这里是第三次网络请求回来的数据处理代码')
console.log('大约有100行')
},1000)
},1000)
},1000)
})
有没有发现,真的是层层嵌套,你现在可能看似逻辑清晰但把数据操作的代码(正常处理+错误捕获)放进去你就会彻底蒙圈了,
深层嵌套+数据逻辑处理
这真的是地狱
我们在来看看使用Promise |
我们知道,执行resolve函数会跳转到then()方法,我们把请求回来的数据进行的操作代码全都放到then()方法里面,最后在代码的结尾return
一个Promise实例继续进行链式操作
reslove.then-return 实例-reslove.then重复下去
new Promise((resolve,reject) => {
setTimeout(()=> {
resolve()
},1000)
}).then(() => {
console.log('这里是第一次网络请求回来的数据处理代码');
console.log('大约有100行');
return new Promise((resolve,reject) => {
setTimeout(()=> {
resolve()
},1000)
}).then(() => {
console.log('这里是第二次网络请求回来的数据处理代码');
console.log('大约有100行');
return new Promise((resolve,reject) => {
setTimeout(()=> {
resolve()
},1000)
}).then(() => {
console.log('这里是第三次网络请求回来的数据处理代码');
console.log('大约有100行');
})
这就是所谓的链式编程
,从嵌套到链式
它的主要特点就是能把原来对异步编程嵌套的写法进行抽离和分离
这种代码可以将数据处理的代码单独放在then或catch里,乍一看可能觉得还没上面好,那是因为对Promise应用的还不熟练,当你开始使用才会发现它的优雅性是名不虚传
Promise.all
我们举一些简单的实例来讲解
我们可能有如下需求:
我们现在有多个网络请求,并且需要所有请求结果都拿到
之后进行下一步操作
我们想要判断两个请求都拿到了,该怎么做呢,平常思维:
并且我们并不能判断哪一个网络请求先拿到,如果已经清楚第一个先拿第二个后拿,那我们只需要在第二个做统一处理就好了,但此时我们只能两个都做处理
设置两个默认为false的变量,某一个请求拿到之后赋值为true,最后做判断两个变量同时为true时进行下一步操作
let isResult1 = false
let isResult2 = false
//请求1
$.ajax({
url:'',
success:function(){
console.log('结果1');
isResult1 = true
handleResult()
}
})
//请求2
$.ajax({
url:'',
success:function(){
console.log('结果2');
isResult2 = true
handleResult()
}
})
function handleResult(){
if(isResult1&&isResult2 ){
//同时为true继续执行操作
}
}
我们再来看看Promise.all |
我们看它的源码可以知道,values:后面看着像数组,不过官方给出的叫可迭代对象
(可遍历,因此传入可以是数组)
Promise.all的特点就在于,你把多个网络请求作为数组写入,它内部会帮你自动判断这几个网络请求操作是否都完成,如果全都完成则会帮你执行.then
Promise.all([
new Promise((reslove,reject) => {
$.ajax({
url:'url1',
success:function(data){
resolve(data)
}
})
}),
new Promise((reslove,reject) => {
$.ajax({
url:'url2',
success:function(data){
resolve(data)
}
})
})//then函数里面传入的是一个数组,并且数组里面包含着两个请求的结果
]).then(array => {
array[0]
array[1]
//此时就可以继续对其操作
console.log('array[0]','array[1]')
})
是不是比上面的操作简单的多,你学废了吗