文章已同步至掘金:https://juejin.cn/post/6844903896486264845
欢迎访问😃,有任何问题都可留言评论哦~
本章介绍三种异步处理方案:
- 回调函数(callback)
- promise
- async/await
如果想要深入了解 promise
、generator+co
、async/await
的话,建议参考我的这篇文章《深入理解 promise、generator+co、async/await 用法》
回调函数(callback)
回调函数应该属于最简单粗暴的一种方式,主要表现为在异步函数中将一个函数进行参数传入,当异步执行完成之后执行该函数
话不多说,上代码:
// 有三个任务console.log(1)console.log(2)console.log(3)
// 过5s执行任务1,任务1执行完后,再过5s执行任务2.....
window.setTimeout(function(){
console.log(1)
window.setTimeout(function(){
console.log(2)
window.setTimeout(function(){
console.log(3)
},5000)
},5000)
},5000)
看出这种方式的缺点了吗?没错,试想,如果再多几个异步函数,代码整体的维护性,可读性都变的极差,如果出了bug,修复的排查过程也变的极为困难,这个便是所谓的 回调函数地狱。
promise
promise简单的说就是一个容器,里面保存着某个未来才会结束的时间(通常是一个异步操作)的结果。从语法上说,promise就是一个对象,从它可以获取异步操作的消息。promise提供统一的API,各种异步操作都可以用同样的方法处理。
如何理解:
- 没有异步就不需要promise
- promise本身不是异步,只是我们去编写异步代码的一种方式
promise有所谓的 4 3 2 1
4大术语
一定要结合异步操作来理解
既然是异步,这个操作需要有个等待的过程,从操作开始,到获取结果,有一个过程的
- 解决(fulfill)指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用 fulfill 来表示解决,但在后世的 promise 实现多以 resolve 来指代之
- 拒绝(reject)指一个 promise 失败时进行的一系列操作
- 终值(eventual value)所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)
- 据因(reason)也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值
3种状态
在异步操作中,当操作发出时,需要处于等待状态
当操作完成时,就有相应的结果,结果有两种:
- 成功了
- 失败了
一共是3种状态,如下:
- 等待态(Pending (也叫进行态)
- 执行态(Fulfilled)(也叫成功态)
- 拒绝态(Rejected) (也叫失败态)
针对每一种状态,有一些规范:
等待态(Pending)
处于等待态时,promise 需满足以下条件:
- 可以迁移至执行态或拒绝态
执行态(Fulfilled)
处于执行态时,promise 需满足以下条件:
- 不能迁移至其他任何状态
- 必须拥有一个不可变的终值
拒绝态(Rejected)
处于拒绝态时,promise 需满足以下条件:
- 不能迁移至其他任何状态
- 必须拥有一个不可变的据因
2种事件
针对3种状态,只有如下两种转换方向:
- pending –> fulfilled
- pendeing –> rejected
在状态转换的时候,就会触发事件:
- 如果是pending –> fulfiied,就会触发onFulFilled事件
- 如果是pendeing –> rejected,就会触发onRejected事件
在调用resolve方法或者reject方法的时候,就一定会触发事件
需要注册onFulFilled事件 和 onRejected事件
针对事件的注册,Promise对象提供了then方法,如下:
promise.then(onFulFilled,onRejected)
针对 onFulFilled,会自动提供一个参数,作为终值(value)
针对 onRejected,会自动提供一个参数,作为据因(reason)
1个对象
promise
注:只有异步操作的结果,可以决定当前是哪一种状态,任何其他的操作都无法改变这个状态
简单来讲,就还是promise中有着三种状态pending,fulfilled,rejected。在代码中我们可以控制状态的变更
new Promise(function(resolve,reject){
console.log("pending");
console.log("pending");
resolve();
reject();
})
创建一个Promise对象需要传入一个函数,函数的参数是resolve和reject,在函数内部调用时,就分别代表状态由pending=>fulfilled(成功),pending=>rejected(失败)
一旦promise状态发生变化之后,之后状态就不会再变了。比如:调用resolve之后,状态就变为fulfilled,之后再调用reject,状态也不会变化
在创建promise对象,只需要根据需求,转换状态即可。无非就是调用两个函数:
- resolve,传递value
- reject,传递reason
Promise对象在创建之后会立刻执行,因此一般的做法是使用一个函数进行包装,然后return一个promise对象
function betray(){
return new Promise(function(resolve,reject){
...//异步操作
})
}
在使用时可以通过promise对象的内置方法then进行调用,then有两个函数参数,分别表示promise对象中调用resolve和reject时执行的函数
function betray(){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve();
},1000)
})
}
betray().then(function(){
...//对应resolve时执行的逻辑
},function(){
...//对应reject时执行的逻辑
})
也可以用 catch 来执行失败态
catch方法,用于注册 onRejected 回调
在这里要明白两件事情:
- catch其实是then的简写,then(null,callback)
- then方法调用之后,仍然返回的是promise对象,所以可以链式调用
使用如下:
betary().then(res=>...//对应resolve时执行的逻辑).catch(err=>...//对应reject时执行的逻辑)
可以使用多个then来实现链式调用,then的函数参数中会默认返回promise对象
betray().then(function(){
...//对应resolve时执行的逻辑
},function(){
...//对应reject时执行的逻辑
})
.then(function(){
...//上一个then返回的promise对象对应resolve状态时执行的逻辑
},function(){
...//上一个then返回的promise对象对应reject状态时执行的逻辑
})
使用promise来解决回调地狱的做法就是使用then的链式调用
function fnA(){
return new Promise(resolve=>{
...//异步操作中resolve
})
}
function fnB(){
return new Promise(resolve=>{
...//异步操作中resolve
})
}
function fnC(){
return new Promise(resolve=>{
...//异步操作中resolve
})
}
fnA()
.then(()=>{
return fnB()
})
.then(()=>{
return fnC()
})
特点是:
- then方法通常是表示异步操作成功时的回调,也可以用catch方法表示异步操作失败时的回调
- 在调用的时候then在前后,catch在后
- then方法可以调用多次,前一个then的返回值,会作为后一个then的参数
- 支持链式调用
async/await
async、await是什么?
async顾名思义是“异步”的意思,async用于声明一个函数是异步的。而await从字面意思上是“等待”的意思,就是用于等待异步完成。并且await只能在async函数中使用
通常async、await都是跟随Promise一起使用的。为什么这么说呢?因为async返回的都是一个Promise对象同时async适用于任何类型的函数上。这样await得到的就是一个Promise对象(如果不是Promise对象的话那async返回的是什么 就是什么);
await得到Promise对象之后就等待Promise接下来的resolve或者reject。
async、await解决了什么?
传统的回调地狱式写法:
getData(a=>{
getMoreData(a,b=>{
getMoreData(b,c=>{
console.log(c)
});
});
});
//不行了,再多写要迷了
Promise改进后的写法:
getData()
.then(a=>getMoreData(a))
.then(b=>getMoreData(b))
.then(c=>getMoreData(c))
async/await改进后:
(async()=>{
const a = await getData;
const b = await.getMoreData(a);
const c = await.getMoreData(b);
const d = await.getMoreData(c);
})();
async、await写法
先来看看同步写法:
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000);
console.log(3);
输出结果:
1
3
2
可以看到输出的顺序并不是我们代码中所写的那样,下面来看下async、await是如何解决这个问题的
(async function () {
console.log(1);
await new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(2);
resolve();
}, 1000);
});
console.log(3);
}())
输出结果:
1
2
3
可以看到这种写法的输出已经符合了我们的预期
async 的定义:
- async函数会返回一个Promise对象
- 如果async函数中是return一个值,这个值就是Promise对象中resolve的值
- 如果async函数中是throw一个值,这个值就是Promise对象中reject的值
await 的定义:
- await只能在async里面
- await后面要跟一个promise对象
常规的promise对象会被js先暂存到eventloop(事件队列)中,因为js是单线程执行的,等执行栈空了之后,才会将事件队列中的事件取出放入执行栈中执行
上述代码中先是将整段代码改造成了一个async(async可以用于任何函数)函数,然后又将setTimeOut改造成了一个Promise对象
使用第三方Promise库
下面简单介绍一下第三方的Promise库
对开发中使用promise进行小结:
- 没有异步,就不需要promise
- 不使用promise,其实也是可以解决异步编程的问题。使用promise,会使异步的编码变得更加优雅,功能会更强
- 在进行promise编程的使用,有如下两个场景:
- 直接使用别人封装好的promise对象,比如fetch、axios
- 需要自己封装promise对象
注意:axios和fetch必须使用promise方式,如:
针对自己封装promise对象,又可以有如下两种方式:
- 自己封装
- 可以使用第三方的promise库
比如,针对第三方的promise库,有两个知名的库:
- bluebird
- q.js
可以利用bluebird 和 q.js 快速的生成promise对象。
以bluebird为例,在服务端演示其用法。
第一步:安装
第二步:使用
评论区