前言
众所周知,JS是一门单线程语言,这就意味着同一时间JS只能处理一个任务。从数据获取,文件读写等等诸如此类的许多操作都要等待资源的响应。这种传统的同步处理方式会阻塞程序的执行,导致资源的浪费和用户的体验感变差。那么我们应该如何解决这个问题呢?
异步处理和同步处理
😈异步处理:异步处理时,程序开始执行一个任务后,不会等待结果的返回,立即执行下一个任务,结果会通过回调函数等方法返回。
😇同步处理:同步处理时,程序开始执行一个任务后,会停止并且等待结果的返回,等接收到结果之后再开始执行下一个任务,依此类推。
setTimeout()
而异步处理通常通过异步函数setTimeout()
来实现。接下来让我们先简单了解一下这个方法。
让我们来一起看一看下面的代码,输出的结果到底是什么呢?
ini
var a = 1
setTimeout(()=>{
a = 2
},1000)
console.log(a);
答案为1 ,因为setTimeout()
为异步函数,它并不会被立即执行,而是被扔进一个事件队列中等待执行。当所有同步函数执行完之后,才会回来检查这个队列并执行相应的回调函数。 以下是执行这段代码的流程:
- var a = 1 :声明变量a = 1
- setTimeout(()=>{a = 2},1000):声明变量a = 2,延迟1秒(1000毫秒)。这个任务被扔进了事件队列,并在1秒后执行。
- console.log(a):输出变量a的值,也就是1。
- 1秒后事件队列中的任务被执行,变量a被赋值为2,但是a已经在1秒前被输出,所以在console.log(a)执行时a依旧为1,即使异步函数
setTimeout()
将a的值改变了。这就是为什么答案为1的原因。
相信现在你已经理解了setTimeout()
方法,那让我们再看一个例子巩固一下:
javascript
var data = null
function a(){ // ajax
setTimeout(function(){
data = 'DDDAMN!'
b()
},1000)
}
function b(){
console.log(data);
}
a()
怎么样?答案是不是一眼就出来了呢?答案为DDDAMN! ,因为 function b()
的作用是输出data , 而 function b()
的调用在function a()
里,所以它也进入了事件队列等待被执行,故输出DDDAMN! 而不是null。
回调地狱
假设有这么一段代码,有一个函数a,它的执行结果要得到函数b的执行返回的结果,函数b的执行结果要得到函数c的执行返回的结果,函数c的执行结果要得到函数d的执行返回的结果。
scss
function a(callbackB,callbackC,callbackD){
callbackB(callbackC,callbackD)
}
function b(callback,callbackD){
callback(callbackD)
}
function c(callback){
callback()
}
function d(){
******
}
a(b,c,d)
函数a 接受三个参数:callbackB
, callbackC
, 和 callbackD
。在这个例子中,callbackB
是 b ,callbackC
是 c ,callbackD
是 d。
当 a 被调用时,它会执行 callbackB(callbackC, callbackD)
。由于 callbackB
实际上是 b ,这相当于调用 b(c, d)。
然后,b 函数接收两个参数:callback
和 callbackD
。在这个调用中,callback
是 c
,callbackD
是 d 。当 b 被调用时,它会执行 callback(callbackD)
。由于 callback
实际上是 c ,这相当于调用 c(d)。
最终,c 函数被调用,它只接受一个参数 callback
。在这个调用中,callback
就是 d 。 当 c 被调用时,它会执行 callback()
。由于 callback
实际上是 d ,这相当于直接调用 d()。
这种情况我们称之为回调地狱
。因为嵌套太深,一旦代码出现问题,将会很难将之排查出来,维护难度太大。就好比一百个串联的灯泡💡,突然全部都不亮了,你就得一个一个去排查到底是哪个灯泡💡出现了问题,简直让人红温。
Promise
Promise
是JS中用于处理异步操作的一种模式,它可以更优雅地来处理异步代码,相比传统的回调函数,Promise
可以更好地控制异步流程,避免回调地狱
。 Promise
有三种状态:pending(进行中) ,fulfilled(已完成) ,rejected(已拒绝) 。 Promise
会接受一个执行器(executor)函数,该函数接受两个参数,分别为resolve 和reject 。接收后Promise
就会变为相应的状态。
下面让我们来看一个例子:
javascript
function a(){ // pending fulfilled rejected
return new Promise((resolve,reject)=>{ // {status: pending -> fulfilled}
setTimeout(()=>{
console.log('我要上A');
resolve() // {status: fulfilled}
},2000)
})
}
function b(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('我要上S');
resolve() // {status: fulfilled}
})
},1000)
}
function c(){
console.log('我要上金S');
}
// a().then(()=>{
// b().then(()=>{
// c()
// })
// })
a().then(()=>{
return b()
}).then(()=>{
return c()
})
答案如下:
按照前面讲的知识,两个setTimeout()
设置的时间分别为1秒和2秒。为什么输出的结果不是
这里就要提到.then()
方法了,他是Promise
原型上的一个函数,.then()
函数会在x这个promise实例对象状态变更为resolved之后才执行内部逻辑,由此借助这个机制可以将异步捋成同步。
上述代码的执行流程是:
-
a() 开始执行,它返回一个新的 Promise。Promise 的执行器函数会在所有的同步代码执行完毕后执行,然后调用 resolve() ,将
Promise
的状态从Pending 改变为Fulfilled。 -
a().then(() => {...}) 被调用。由于 a() 返回的
Promise
还没有完成,.then()
方法会返回一个新的 Promise 并等待 a() 的Promise
完成。当 a() 的Promise
完成时.then()
中的回调函数会被执行。 -
.then(() =>{...}) 被调用,由于 b() 的调用,b() 同样返回一个 Promise。因此,这个
.then()
的回调函数会等待 b() 的Promise
完成。当 b() 的Promise
完成后,链式调用的下一个.then()
被触发。然后返回 c() 的调用输出结果。
resolve(x) 这个x会指定交给then()
中的回调函数。
javascript
var data = null
function a(){
return new Promise(function(resolve, reject) {
setTimeout(function(){
console.log('a is done');
data = 'M3'
resolve('DDAMN')
},1000)
})
}
function b(){
console.log(data);
}
a().then((res)=>{
b()
console.log(res);
})
结果如下:
reject(x) 这个x会指定交给catch()
中的回调函数,catch()
是专门来捕获程序中的错误的。 当然也可以通过 reject() 改变状态:
javascript
var data = null
function a(){
return new Promise(function(resolve, reject) {
setTimeout(function(){
console.log('a is done');
data = 'M3'
reject('DAMN')
},1000)
})
}
function b(){
console.log(data);
}
a.catch((err)=>{
console.log(err);
});
结果如下:
那么让我们再用Promise
解决前面的回调地狱
吧:
scss
function a() {
return new Promise((resolve) => {
b(resolve, c, d);
});
}
function b(callback, callbackC, callbackD) {
return new Promise((resolve) => {
callbackC(resolve, callbackD);
});
}
function c(callback, callbackD) {
return new Promise((resolve) => {
callbackD();
resolve();
});
}
function d() {
console.log('D is done');
}
// 使用 Promise 的链式调用
a().then(() => b())
.then(() => c())
.then(() => d());
是不是学会了呢?
希望这篇文章能帮助你更好的理解Promise
,并加以运用到实战当中。🥳