【JS】定时器、同步异步、Promise

3,023 阅读8分钟

JS比较难的几个部分:数据类型、作用域、面向对象、同步异步...

定时器

定时器是异步的

当浏览器解析到定时器时,浏览器会记录一下当前的时间,等过了你设置的时间以后会让回调函数执行

定时器的返回值:1、2....

可以用一个变量接收定时器的返回值,这样在清除定时器的时候比较好区分,变量里存储的就是该定时器的编号,是数字类型的。

定时器的编号是按顺序往后排的,两种定时器不做区分,

返回值代表的是当前js中的第几个定时器,两种定时器的形式不做区别,就认为两种是一样的就行

定时器是函数类型的

setTimeout
setInterval

清除定时器

clearTimeout
clearInterval

这两种都可以清除两种定时器,用的时候不做区别

清除定时器的时候,小括号里面写定时器的编号,清除哪个定时器就写对应编号,一般会用一个变量存储着编号,清除定时器时小括号里面写这个变量即可,直接写编号也可以,编号是数字类型的

比如
clearInterval(1) 就是清除第一个定时器
--------------------------------------
如果在设置定时器的时候用了
timer = setInterval();//用timer接收了返回值,也就是编号
在清除定时器的时候,就可以
clearInterval(timer)

浏览器有最小的识别时间,根据浏览器类型的不同反应时间不同,谷歌快一点,ie慢

在定时器的第三个参数开始可以给回调函数传实参,不可以设置自执行函数传参,那样就起不到定时器的作用了

同步异步

浏览器是多线程的,可以同时执行多个任务

JS是单线程的,JS运行同一时间只能执行一行代码

进程和线程:一个进程可以包含很多线程

浏览器的一个页面就是一个进程,一个进程同时可以做很多很多事,一个线程同时只能做一件事

当JS在解析代码时,当遇到异步的代码时,会把这个异步的任务放到等待队列中,遇到同步的代码,会放在主任务队列中,当主任务队列的代码都执行完以后,才会执行等待任务队列中的代码(就算定时器设置的时间超时了也得等着)

当JS在解析代码时,不会在此等待,而是继续执行下面代码 当同步代码执行完以后,会执行异步任务中的代码,会让先到达目标时间的任务先执行,如果目标时间相同,那就按谁先来的就先执行谁了,因为快了那么一点点

当同步代码执行完以后,等待队列里面的任务在执行时,不是看的谁先来的,而是看谁设置的执行时间先到的就先执行谁。

当同步代码执行完,会去等待队列中找最先到达执行执行时间的异步任务,让其执行,不是单纯的比大小,具体情况具体分析,看人家谁先到的该执行的时间就得执行谁了。

等待队列本身不执行代码的,想要执行队列中的代码的时候,把它拿回栈里面进行执行。

Promise

初识Promise

Promise是ES6新增的

Promise是异步编程的一种解决方案,解决了层层的回调,层层回调也叫回调地狱,比如有多个请求,后面的请求都需要前一个请求返回来的数据才能进行

Promise 是一个函数,是一个类

Promise类必须传一个函数

给Promise传入了一个实参,function,这个函数是执行的

在执行Promise时,执行了这个回调函数,只要new Promise,函数就会执行

这个函数是同步的,这个代码不执行完,不会执行下面的代码

Promise的 then 和里面的回调函数是异步执行的

let p1 = new Promise(function(){ });
console.log(p1);

p1是Promise的一个实例

p1中有代表该实例的状态

默认生成p实例就是pending状态

pending 是等待状态

fulfilled 成功状态

rejected 失败状态

只有两种状态的转换情况:

pending ==>fulfilled

pending ==>rejected

而且是不可逆的

在Promise中一旦状态发生改变,就会凝固,状态就不会再改变了,定型了

let p1 = new Promise(function(resolve,reject){
console.log(resolve,reject)
});

resolve 和 reject是回调函数function的形参,在Promise构造函数执行时,resolve 和 reject 也都是一个函数,在执行的时候内部自己传的实参,传的是函数

let p1 = new Promise(function(resolve,reject){
resolve();
});
console.log(p1);

执行了resolve,那么该Promise实例的状态就从pending状态变成了fulfilled状态

let p1 = new Promise(function(resolve,reject){
reject();
});
console.log(p1);

执行reject让Promise实例变成失败状态

Promise的then

p1是Promise实例,在Promise的原型上有个then方法

p1.then(function(){
console.log(1)
},function(){
console.log(2)
})
console.log(p1)

then是执行了的,如果p1是pending状态,then的两个实参都不执行

then:

1、状态从pending ==>fulfilled是第一个函数执行

状态从pending ==>reject是第二个函数执行

2、then绑定的回调函数是异步的

3、resolve执行传递过来的实参,会传给then的形参函数

本质还是异步的,只是把异步的请求以同步的方式表现出来

第一个请求成功了以后,才能执行第二个请求,第二个请求成功了以后,才能执行第三个

每一个then都有一个成功的回调和一个失败的回调

4、p1.then()的返回值也是一个Promise的实例,所以可以链式调用then方法

let p1 = new Promise(function(resolve,reject){
setTimeout(function(){
  resolve();
},1000)
});
p1.then(function(){
console.log(1)
},function(){
console.log(2)
})

Promise构造函数执行的时候是同步的,但是函数里面可以有异步的任务,比如里面可以写个定时器、ajax等异步的任务。

let p1 = new Promise(function(resolve,reject){
console.log(a)
});
p1.then(function(){
console.log("成功")
},function(){
console.log("失败")
})

1、执行reject变成失败 2、Promise构造函数执行时,函数中代码报错,但是控制台不会显示报错,只会让then的失败的回调函数执行 (这里和 try catch finish那个有关)

那么后面的then是怎么判断执行第一个回调函数还是第二个回调函数呢

let p1 = new Promise(function(resolve,reject){
  resolve();
});
p1.then(function(){
console.log("成功1")
},function(){
console.log("失败1")
}).then(function(){
console.log("成功2")
},function(){
console.log("失败2")
})
console.log(p1);

new Promise执行时里面的resolve和reject只能控制第一个then执行成功的还是失败的回调函数,每个then执行返回的Promise实例都会默认从pending到成功状态fulfilled,所以后面的then不管开始的new Promise里面执行的是resolve还是reject,只要前面紧邻的then的成功或失败回调顺利执行完毕,都是默认return成功状态的Promise实例,然后调用后面的then执行,后面的then都是默认执行成功的回调函数。

但是也可以手动控制后面的then是成功还是失败状态:

let p1 = new Promise(function(resolve,reject){
  resolve();
});
p1.then(function(){
console.log("成功1");
return new Promise(function(resolve,reject){
  reject();
});
},function(){
console.log("失败1")
}).then(function(){
console.log("成功2")
},function(){
console.log("失败2")
})
console.log(p1);

如果在then的回调中返回了Promise实例,那么下一个then会受返回的Promise的状态的影响

这样来看就是:一个Promise实例控制一个then。如果想在前一个then的成功的回调函数控制下一个then 就在其成功的回调函数结尾return一个Promise实例,在Promise的回调函数里面控制下一个then执行成功还是失败的回调函数,失败的回调函数同理。

当然,理论上是这样的,但是实际应用时是第一个请求成功以后发第二个请求,第二个成功发第三个。

成功才会有相应的操作和处理

注意: 如果前面的then里面代码执行报错,也会把状态改为失败态,后面的then执行失败的回调

Promise的catch

在真实的项目中,咱们只需要成功 ,捕获一下失败的

let p1 = new Promise(function(resolve,reject){
  reject();
});
p1.then(function(){
console.log("成功1");
},function(){
console.log("失败1")
}).then(function(){
console.log("成功2");
},function(){
console.log("失败2")
}).catch(function(err){
console.log(err)
})

真实的项目中是这样的:只写成功的情况执行的回调函数,不写失败的回调函数,然后在链式的最后面结尾处加个catch

let p1 = new Promise(function(resolve,reject){
  resolve();
});
p1.then(function(){
console.log("成功1");
console.log(num);
}).then(function(){
console.log("成功2");
}).catch(function(err){
console.log(err)
})

catch就是捕获错误或者失败的,前面只要有失败就捕获

catch是捕获到第一个失败,只要失败了,下面都不再执行了

一般都是把catch写在最后,写在链式的最后,不然catch就没有意义了

catch捕获的失败都是前面的then的失败的,后面再有then的话,失败了就捕获不到了,所以一定要把catch写在最后面来捕获前面的失败,捕获到第一个失败以后,后面的then不再执行

let p1 = new Promise(function(resolve,reject){
  resolve();
});
p1.then(function(){
console.log("成功1");
console.log(num);
}).then(function(){
console.log("成功2");
},function(){
console.log("失败2")
}).catch(function(err){
console.log(err)
})

还有需要注意的是:如果then有失败的回调函数,那么就不再执行catch了,catch就不捕获了

总结起来就是:then里面有失败回调,失败了就执行失败回调,无失败回调,失败了就执行catch