js之异步编程和事件循环机制
异步编程
js是单线程事件循环模型,同步操作与异步操作时代码所依赖的核心机制。异步行为是为了优化因计算量大而时间长的操作。
同步:当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,那么这个进程会一直等下去,直到请求返回。
异步:当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,那么这个进程会直接向下执行,不需要等待,当消息返回时再通知进程处理。
Promise
Promise的出现主要时解决之间异步编程使用回调函数的回调地狱的问题。
Promise的状态
Promise有三种状态:
- pending:待定
- fulfilled:成功
- rejected:失败
两种状态变化:
- pending -> fulfilled:在promise中调用resolve()函数会产生此状态转变
- pending ->rejected:在promise中调用reject()函数会产生此状态转变
promise的基本流程
Promise构造函数
用于创建Promise对象,需要传入一个执行器函数,函数中需要带两个参数:resolved,rejected;用于修改Promise的状态
let p = new Promise((resolved,rejected)=>{
console.log(1);
resolved();
})
// 1
console.log(p); //Promise {<fulfilled>: undefined}
Promise.prototype.then()
用于定义成功或失败状态的回调函数,并返回一个新的Promise。
let p = new Promise((resolved,rejected)=>{
setTimeout(()=>{
resolved("ok");
},1000);
});
p.then(res=>{
console.log(res);
},err=>{
console.log(err);
})
// ok
let p = new Promise((resolved,rejected)=>{
setTimeout(()=>{
rejected("err");
},1000);
});
p.then(res=>{
console.log(res);
},err=>{
console.log(err);
})
// err
Promise.prototypr.catch()
用于定义失败的回调,then方法的语法糖,相当于.then(undefined,onRejected);
let p = new Promise((resolved,rejected)=>{
setTimeout(()=>{
rejected("err");
},1000);
});
p.then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
// err
Promise.resolve()
返回一个成功或者失败的Promise;
let p = Promise.resolve("ok"); //Promise {<fulfilled>: ok}
let p = Promise.resolve(Promise.reject('err')); //Promise {<rejected>: 'err'}
Promise.reject()
返回一个失败的Promise
let p = Promise.rejected("err"); //Promise {<rejected>: err}
let p = Promise.rejected(Promise.resolve('ok')); //Promise {<rejected>: 'ok'}
Promise.all()
返回一个新的Promise,只有所有的Promise都成功才成功,否则有一个失败就返回失败的promise
let p = Promise.all([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve()
]) // Promise {<fulfilled>: [1,2,undefined]}
let p = Promise.all([
Promise.reject('err'),
Promise.resolve(1),
Promise.resolve(2)
]) // Promise {<rejected>: 'err'}
Promise.race()
返回一个新的Promise,第一个完成的Promise的结果状态就是最终的结果状态。
let p = Promise.race([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve()
]) // Promise {<fulfilled>: 1}
let p = Promise.all([
Promise.reject('err'),
Promise.resolve(1),
Promise.resolve(2)
]) // Promise {<rejected>: 'err'}
Promise原理实现
function Promise(excutor){
const self = this;
self.status = "pending";
self.data = undefined;
self.callbacks = [] // 用来保存所有待调用的回调函数
function resolve(value){
if(self.status !== "pending"){
return;
}
// 改变状态
self.status = "fulfilled";
self.data = value;
// 异步调用回调函数
if(self.callbacks.length > 0){
setTimeout(()=>{
self.callbacks.forEach((obj)=>{
obj.onResolved();
})
})
}
}
function reject(reason){
if(self.status !== "pending"){
return;
}
// 改变状态
self.status = "rejected";
self.data = value;
// 异步调用回调函数
if(self.callbacks.length > 0){
setTimeout(()=>{
self.callbacks.forEach((obj)=>{
obj.onRejected();
})
})
}
}
// 立即同步调用执行器函数
try{
excutor(resolve,reject);
}catch(error){
reject(error);
}
}
Promise.prototype.then = function(onResolved,onRejected){
const self = this;
onResolved = typeof onResolved === 'function'?onResolved:value=>value;
onRejected = typeof onResolved === 'function'?onRejected:reason=>{throw reason};
// 返回promise对象
return new Promise((resolve,reject)=>{
// 执行回调函数
function handle(callback){
try{
const x = callback(self.data);
// 如果返回对象为promise
if(x instanceof Promise){
x.then(resolve,reject);
}else{
resolve(x);
}
}catch(err){
reject(err);
}
}
// 定义回调之前,状态已经改变
if(self.status == 'fulfilled'){
setTimeout(()=>{
handle(onResolved);
})
}else if(self.status == 'rejected'){
setTimeout(()=>{
handle(onRejected);
})
}else{
// 定义回调之前状态还未改变
self.callbacks.push({
onResolved(){
handle(onResolved);
},
onRejected(){
handle(onRejected);
}
})
}
})
}
Promise.prototype.catch = function(onRejected){
return this.then(undefined,onRejected)
}
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
if(value instanceof Promise){
value.then(resolve,reject);
}else{
resolve(value);
}
})
}
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let count = 0;
let plen = promises.length;
let values = new Array(plen);
for(let i = 0;i < plen;i++){
Promise.resolve(promises[i]).then(value=>{
count++;
value[i] = value;
if(count == plen) resolve(values);
},err=>{
reject(err);
})
}
})
}
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i = 0;i < promises.length;i++){
Promise.resolve(promises[i]).then(value=>{
resolve(value);
},err=>{
reject(err);
})
}
})
}
异步函数async/await
ES8提出使用async和await解决异步结构组织代码的问题
async
async关键字用于声明异步函数,使用async关键字可以让函数具有异步特征,但总体上其代码仍然时同步求值。
async function fn1(){
console.log(1);
}
fn1();
console.log(2);
// 1
// 2
async的返回值是一个Promise对象,如果函数中返回一个值value,则async则返回Promise.resolve(value);
await
因为异步函数主要针对不会马上完成的任务,所以自然需要一种暂停和恢复执行的能力。
await关键字必须在异步函数中使用。
async/await中真正起作用的是await。async关键字,无论从哪个方面来看,都是一个标识符。异步函数中不包含await关键字,其执行基本上跟普通函数没有区别。
async function fn(){
console.log(1);
let p = await Promise.resolve('ok');
console.log(p);
console.log(2);
}
fn();
console.log(3);
// 1
// 3
// ok
// 2
js事件循环机制
js运行机制
因为js时单线程的,在执行代码时,将不同函数的执行上下文压入栈中来保证代码的有序执行,在执行同步代码的时候,如果遇到异步代码,js并不会等待其返回结果,而是将异步代码交给对应的异步代码处理线程处理,继续执行栈中的任务。
当异步事件执行完毕之后,再将异步事件对应的对调加入异步执行队列,当主栈中的函数执行完毕后,会从异步执行队列中选择任务来执行。
宏任务和微任务
在任务队列中异步回调分为2中:微任务和宏任务。也就是说任务队列可以分为两种:微任务队列和宏任务队列。
宏任务:全局代码,settimeout/setinterval,UI渲染,
微任务:promise.then,catch,finally,process.nextTick
宏任务和微任务的执行顺序
-
代码从开始执行调用全局执行栈,script标签内的代码做为宏任务执行。
-
执行过程中同步代码立即执行,异步代码由异步处理线程处理然后放入任务队列中。
-
执行过程中同步代码立即执行,异步代码放入任务队列中。
-
先看任务队列中的微任务队列是否存在微任务
有微任务:执行微任务队列中的所有微任务
微任务执行过程中所产生的微任务放到微任务队列中,继续执行。
-
如果没微任务,查看是否具有宏任务,有的话执行,没有的话事件轮询结束。
执行过程中所产生的微任务放到微任务队列中。
完成单个宏任务之后,执行微任务队列中的任务。
-
常见面试题
异步编程的实现方式有哪些
- 回调函数:使用回调函数的方式的缺点是,是个回调函数嵌套的时候,会照成回调函数地狱,上下两层的回调函数间的代码耦合度太高,不利于代码维护。
- promise:使用promise的方式可以嵌套的函数调用做为链式调用,但是使用这种方法,有时会找出多个then的链式调用,可能造成代码的语义不够明确。
- generator:配合yield实现,可以在函数内实现中断。
- async/await:async函数是generator和promise实现的一个自动执行的语法糖,他内部自带执行器,当函数内部执行到await语句的时候,如果语句返回promise对象,那么函数将会等待promise对象的状态变为resolve后再基于向下执行。因此可以将异步逻辑转化为同步的顺序来写,并且这个函数可以自动执行。
如何改变promise的状态
- 调用resolve(value),pending ->fulfilled
- 调用reject(reason),pending ->rejected
- 抛出异常:如果当前时pending就会变成rejected
改变promise状态和指定回调函数谁先谁后
都有可能,正常情况下是先指定回调再改变状态,但也可以先改变状态再指定回调。
什么时候能得到数据?
- 先指定回调,当状态发生改变时,回调函数就会调用,得到数据。
- 先改变状态,当指定回调函数时,回调函数就会调用,得到数据。
promise中改变状态和指定回调函数时做了什么事情
在promise内部定义了一个回调队列。
在状态改变时,也就是调用onresolved或者onrejected函数时,会执行回调队列中的函数。
当指定回调函数时,首先会判断当前的状态是否是pending,如果是则将回调函数加入回调队列,否则直接执行根据状态执行onresolved或者onrejected函数。
这就是为什么无论是先改变状态还是先指定回调函数,都能得到最终的结果的原因。
promise.then() 返回的新promise的结果由什么决定
-
如果抛出异常,新的promise变为reject,reason为抛出的异常
-
如果返回的时非promise的任意值,新promise变为fulfilled,value为返回的值
-
如果返回promise的时另一个promise对象,此peomise的结果就会成为新promise的结果。
promise异常穿透
- 当使用promise的then链式调用时,可以在最后指定失败的回调
- 前面任何操作处理异常都会传到最后的回调中处理
中断promise链
当使用promise的then链式调用,在中间中断,不在调用后面的回调函数。
办法:在回调函数中返回一个pending状态的promise对象
async/await对比promise的优势
- 代码读起来更像同步,Promise虽然摆脱了回调地狱,但是then方法的链式调用也会带来额外的阅读负担
- Promise传递中间值非常麻烦,而async、await几乎是同步的写法
Promist.catch后面的.then还会执行吗
.then回执行,因为catch方法返回的还是一个promise对象,依然支持链式调用。
Promise中,resolve后面的语句是否还会执行?
会被执行。如果不需要执行,需要在 resolve 语句前加上 return。
JS为什么是单线程语言
JavaScript的诞生就是为了处理浏览器网页的交互(DOM操作的处理、UI动画等), 设计成单线程的原因就是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果(两个线程修改了同一个DOM节点就会产生不必要的麻烦),这对于一种网页脚本语言来说这就太复杂了。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
JS是怎么实现异步异常运行的
JavaScript是单线程的,但它所运行的宿主环境—浏览器是多线程,浏览器提供了各种线程供Event Loop调度来协调JS单线程运行时不会阻塞
谈一谈setInterval的问题
setTimeout的作用是每隔一段指定事件执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。
所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。
正对这种现象,我们可以使用setTimeout递归调用来模拟实现setInterval,这样就能确保了只有一个事件结束,我们才会触发下一个定时器事件。
function mySetInterval(fn,delay){
var timer = {
flag = true;
};
function interval(){
if(timer.flag){
fn();
setTimeout(interval,delay);
}
}
setTimeout(intetval,delay);
return timer;
}
function myClearInterval(timer){
timer.flag = false;
}
转载自:https://juejin.cn/post/7235906062310244408