前言
我们时常在JS中遇到要接受数据或者遇到要时间来执行的代码后,再进行一系列操作的情况,接受数据和要时间执行的代码通常都需要一些时间等待,而有些代码后续的执行起来又不需要,今天我们就来细细聊聊遇到这种情况我们该怎么办。
认识异步
let a = 1
console.log(a, 2);
setTimeout(() => {
a = 2
console.log(a, 6);
}, 1000);
console.log(a, 9);
//输出 :1 2
// 1 9
// 2 6
我们可以看到先输出了第二行,再输出第九行,再输出第六行。欸?代码的执行不是按照从上往下依次执行的吗,为什么我先执行了第九行的输出呢?
恭喜你!你发现了异步:
JS遇到需要耗时执行的代码就将其先挂起,等到后续不耗时的代码执行完毕后再执行耗时的代码
有的小伙伴肯定就要说了,为什么JS要这么设计,为啥就不能按照顺序先把要时间的代码先执行完,再执行后续的代码应该更符合大家的习惯?
我们要知道的是
js是单线程语言,一次只能干一件事情,如果只能硬着头皮按照顺序去执行代码,你想象这种画面:前面的代码巨巨巨巨耗时就比如说for循环个几万次,但是后面的代码和前面完全无关,就因为你一个for循环让大家都跟着你排队是不是不太好啊,官方一定会被戳脊梁骨的。因此它被设计成先执行不耗时的代码,再执行耗时的代码的机制,这样的效率就是最高的。
解决异步
官方既然这么设计,一身反骨的我就是要让耗时的代码先执行,有没有什么办法嘞?
答案当然是有的,回调函数和promise就是解决异步问题的专家,现在我们来认识一下。
回调函数
function a(cb) {//callback回调函数
setTimeout(() => {
console.log('a执行完毕');
cb()
}, 1000)
}
function b() {
console.log('b执行完毕');
}
a(b)
//输出: a执行完毕
// b执行完毕
我们将要耗时的代码和不耗时的代码都各自放进两个函数里面,将不耗时代码的函数b作为实参,传给要耗时代码的函数a,在你想什么时候要让b执行的时候加上cb(),调用b自然就会在a执行完之后执行b,解决了异步问题。
但是在实际开发中如果要耗时函数有点多出现函数的嵌套,我们又要让它不出现异步问题,我们使用回调函数试试看。
function a(cb, cb2, cb3) {//callback回调函数 复杂,不好排查错误
setTimeout(() => {
console.log('a执行完毕');
cb(cb2, cb3)
}, 1000)
}
function b(cb, cb3) {
setTimeout(() => {
console.log('b执行完毕');
cb(cb3)
}, 1000)
}
function c(cb) {
setTimeout(() => {
console.log('c执行完毕');
cb()
}, 1000)
}
function d() {
console.log('d执行完毕');
}
a(b, c, d)
//输出:a执行完毕
//b执行完毕
//c执行完毕
//d执行完毕
这一大串串的,写这里写一半我都觉得复杂了,我要在a里面传三个参数,在它肚子里面的callback回调函数我又要传两个参数,头大,写一半的时候还突然出错了,我还不知道是哪里出错,这就是使用回调函数的缺点,也就是业内所说的回调地狱:
如果函数嵌套过深,维护成本大,一旦出现错误难以排查
那我们还能用什么来解决异步问题吗? 这时候超级英雄promise来救场啦!
promise
我们来模拟一个场景,章总是一个视瓦为性命的瓦友(p),他一起床,那得干嘛,先吃个饭呗,不然就没劲打瓦了
function getup() {
setTimeout(() => {
console.log('章总起床了')
}, 2000)
}
function eat() {
setTimeout(() => {
console.log('章总吃饭了')
}, 1000)
}
getup()
eat()
//输出章总吃饭了
//章总起床了
章总简直就是超人,他还没有起床就能吃饭,哈哈哈哈,不扯淡了。我们来看这段代码,因为两个函数都是耗时的,我们虽然先调用getup再调用eat由于eat所需时间更短eat先执行,这个以后会在事件循环里面提到代码执行的顺序,接下来我们用promise来解决这个问题
function getup() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('章总起床了')
resolve('起床成功')//没有这个resolve(),后面代码不执行
}, 2000)
})
}
function eat() {
setTimeout(() => {
console.log('章总吃饭了')
}, 2000)
}
getup()
.then((res) => {
console.log(res)
eat()
})
//章总起床了
//起床成功
//章总吃饭了
来看看我们加了promise和之前的代码有什么不一样,我们在要先执行的代码里面加了一个return new Promise巴拉巴拉的一段固定句式,又多加了一个resolve(),然后在执行阶段,哦呦好像不太一样哦,我们用到了.then()方法,我先来大致介绍一下promise的用法:
promise用法
在耗时代码里面加上return new Promise( (resolve,reject) =>{函数体内容},我们可以看到这里Promise是大写的,很明显它是构造函数,我们这函数的返回值是构造函数Promise new 出来的实例对象,函数里面有两个形参resolve和reject
二者对promise可谓是功不可没,如果没有调用这二者,则返回出来的实例对象状态就是Pending(待定),它无法做操作。
如果在函数体调用了resolve(value)对象的状态就会变成Fulfilled(已完成),在已完成状态下Promise对象可以使用.then方法,并传递结果 value
如果在函数体调用了reject(reason)对象的状态就会变成Rejected(已拒绝),在已拒绝状态下Promise对象可以使用.catch方法,并传递错误原因 reason
注意:Promise 的状态一旦从 Pending 变为 Fulfilled 或 Rejected,就不可再改变。因此,Promise 状态是单向的:从 Pending 转变为 Fulfilled 或 Rejected。这意味着你无法从一个已完成的 Promise 重新回到 Pending 状态。
我们来看看reject的情况
function a() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log('a');
// resolve('a执行完毕');
reject('a执行失败了')
}, 1000)
});
}
a()
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
//输出a
//a执行失败了
接下来由我们的promise来挑战回调地狱面临的问题,章总打瓦流程
function getup() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('章总起床了')
resolve('起床成功')//没有这个resolve(),后面代码不执行
}, 2000)
})
}
function eat() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('章总吃饭了')
resolve('吃饭成功')
}, 1000)
})
}
function happy() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('章总打瓦了')
resolve('打完了')
}, 3000)
})
}
function record() {
console.log('章总0/21/0孤独carry')
}
getup()
.then((res) => {
console.log(res)
return eat()
})
.then((res) => {
console.log(res)
return happy()
})
.then((res) => {
console.log(res)
record()
})
//输出:章总起床了
//起床成功
//章总吃饭了
//吃饭成功
//章总打瓦了
//打完了
//章总0/21/0孤独carry
我们前面说了Promise实例对象在调用了resolve(value)对象的状态就会变成Fulfilled(已完成) ,在已完成状态下Promise对象可以使用.then方法,因此我们可以让第一个.then方法返回eat(),也就是返回了一个状态为Fulfilled(已完成) 的promise,因此我们可以继续接一个.then方法,依靠此方法我们能让这些函数像一条链子一样依次执行,不需要给过多的参数,代码写起来也十分优雅,甚至我们可以添加 catch 方法来捕获可能发生的错误,来更好地排查和处理异常情况。
欢迎大家的交流与指正看到这里了,就不妨动动手点个赞吧,谢谢大家