异步
js代码执行引擎是单线程的,但是渲染进程是有多个线程,异步代码就是将异步操作交给这些线程完成,而js引擎继续向后执行js代码:
- JS引擎线程
- 定时触发器线程
- 异步http请求线程
- GUI渲染线程
js任务分为宏任务和微任务:
- 宏任务包括:整体代码块、setTimeout、setInterval、I/O操作、UI渲染等,执行后立马执行微任务
- 微任务包括:Promise的回调函数、MutationObserver等
事件循环的执行过程
- 执行栈中的宏任务(如同步代码块)被执行。
- 宏任务执行完毕后,事件循环会检查微任务队列,并
清空队列中的所有微任务。- 如果有必要,浏览器会进行渲染更新。
- 事件循环会检查宏任务队列,如果有宏任务,就取出并执行。
- 事件循环不断重复上述步骤。
const promise1 = new Promise((resolve, reject) => {
console.log('这里是同步代码,直接执行');
resolve();
});
promise1.then(() => {
console.log('这里会在promise变为落定状态后加入微任务队列,等待同步代码执行完后执行')
});
console.log("promise1", promise1);
// 这里是同步代码,直接执行
// promise1 Promise {<fulfilled>: undefined}
// 这里会在promise变为落定状态后加入微任务队列,等待同步代码执行完后执行
setTimeOut
定时器:一旦到期,就将传入的函数或代码放入宏任务队列,等待当前执行队列结束并清空微任务队列后依次调入执行
- setTimeOut(code,[delay]);
- code为函数定义
- delay默认为0,单位为毫秒
- setTimeOut(functionRef,[delay,arg1,arg2...]);
- 传入的是函数引用
- 到期后并不是立马执行,可能会滞后很多
- 在调用
setTimeout()的线程结束之前,函数或代码片段不能被执行。 - 函数返回的是timeoutID,用于clearTimeout()来取消定时器
- setInterval是用于到点后重复执行代码,用clearInterval()来清理
setTimeout(()=>{console.log('计时器')});
console.log('正常执行');
//正常执行 计时器
期约Promise
尚不存在结果的替身
本质:有特殊行为的对象:对象存在状态,当状态为resolved(fulfilled)和rejected时,会将相应的处理程序调入微任务队列,等待执行
构建
const p1 = new Promise(执行函数);其中执行函数接受两个参数,
resolve和reject
const p2 = Promise.resolve(value);
const p3 = Promise.reject(reason);
状态
- 待定pending,可以由这个状态在
执行函数中调用方法,变为另外两个状态,但是注意并不是所有的Promise都起始为这个状态,当然也并不是所有Promise都会脱离此状态。 - 兑现fulfilled(resolved),落定状态,不可变为其他状态,拥有一个私有的内部值(默认为undefined),此状态后会调用then中的处理程序并传递私有内部值。
- 拒绝rejected,落定状态,不可变为其他状态,拥有一个私有的内部理由(默认为undefined),会调用then中的对应处理程序并传递理由
const p1 = new Promise(()=>{});
const p2 = Promise.resolve('1');
const p3 = Promise.reject('e');
console.log(p1,p2,p3);
//Promise { <pending> } Promise { '1' } Promise { <rejected> 'e' }
执行函数
传入构造函数Promise中的函数,且是
同步调用的
- 传入resolve,reject,这是两个函数,调用后会将Promise对象状态分别变为兑现和拒绝,且reject(reason)调用后,会抛出
异步错误,无法使用try/catch捕获 - resolve(value)和reject(reason)调用一次后,状态就不会再改变了,即使执行函数后面还有不同的函数调用,只会静默失败
const p1 = new Promise((resolve,reject)=>{
console.log('这是立即执行');//这是立即执行
setTimeout(resolve,1000,'value');//虽然后面执行了,但是由于状态已经变为了rejected,所以静默失败
reject('error');//直接就将p1状态变为了rejected
})
console.log(p1);//Promise { <rejected> 'error' }
静态方法
1.Promise.resolve(值)
实例化一个已解决的Promise,该Promise的值就是传入的值(值是拒绝的期约时并不是已解决的期约)
- 值可以是任何类型,包括错误对象Error实例
- 如果传入的值是期约,则该方法就是一个空包装,返回传入的值
- 特殊:传入的是拒绝的期约,则返回的就是拒绝的期约
const p1 = Promise.resolve(new Error('错误'));
console.log(p1);//Promise {Error: 错误},这就是已解决的期约
const p2 = Promise.resolve(Promise.reject(2));
console.log(p2);//Promise { <rejected> 2 },返回的依然是未解决的期约
2.Promise.reject(理由)
实例化未解决的期约,同时还会抛出异步错误 理由也可以是任何类型,同时如果是期约,则其理由就是传入的期约
const p1 = Promise.reject(new Error('错误'));
console.log(p1);//Promise { <rejected> Error: 错误 }
const p2 = Promise.reject(Promise.reject(2));
console.log(p2);//Promise { <rejected> Promise { <rejected> 2 } }
3.Promise.all/Promise.race(期约数组)
见期约合成
实例方法
thenable接口
对象实现了then()方法
then(onResolved处理程序,onRejected处理程序)
会返回一个新期约,如果还没有执行处理程序,处于待定;执行后,根据处理程序返回值(抛错)切换状态 当期约状态落定后,会将对应的处理程序加入微任务队列中,处理程序调用时默认传入值(理由)
- then函数可以不止一个
- 当参数传入不是函数时,会静默处理
- 可以只传入一个参数,当要传入后者时,需要在前者留下占位符
- 处理程序执行后会修改新期约的值:
- 调用任意处理程序,则使用Promise.resolve(返回值)来包装,没有返回值则是undefined,但是注意如果处理程序中抛出错误,则返回拒绝的期约
- 如果没有传入处理程序,则将原始期约返回
const p1 = new Promise((resolve)=>{
resolve();
})
let p2 = p1.then(()=>{
return '值';
})
let p3 = p1.then(()=>{
throw '抛出错误';
})
p3.catch(()=>{});
setTimeout(console.log,0,p2);//Promise { '值' }
setTimeout(console.log,0,p3);//Promise { '值' }
const p4 = new Promise((resolve,reject)=>{
reject('error');
})
const p5 = p4.then();//没有处理程序,p4就传给p5
p5.catch(()=>{});
const p6 = p4.then(null,()=>{
return '值';
})
setTimeout(console.log,0,p5);//Promise { <rejected> 'error' }
setTimeout(console.log,0,p6);//Promise { '值' }
catch(onRejected处理程序)
其实就是then(null,onRejected处理程序)语法糖
finally(处理程序)
无论原期约落定状态是什么,都会执行处理程序
期约连锁与合成
前者多个期约串联,后者多个期约并连
期约连锁
- 原理:由于then返回的是新期约,且会使用Promise.resolve()包装,所以可以通过then().then()把多个期约串联起来,这样是有顺序的执行
Promise.resolve('1')
.then(value => {
console.log(value); // 输出 '1'
return '2';
})
.then(value => {
console.log(value); // 输出 '2'
return '3';
})
.then(value => {
console.log(value); // 输出 '3'
});
期约合成
Promise.all(可迭代对象)
也是创建期约,只是创建的期约落定状态与传入的所有期约有关;对传入的期约起一个监视的作用,注意期约在创建的时候就已经启动,而不是在传入all里面才启动
- 会对传入的元素使用Promise.resolve()包装
- 只有传入的期约都解决了,才会解决,如果有一个为拒绝/待定,则创建的期约也为拒绝/待定
- 如果新期约为解决,则其值为所有期约的值,按照迭代顺序,内部期约执行时是并发执行的
- 如果为拒绝,则新期约理由是第一个拒绝的期约的理由,同时内部期约都会继续执行下去,不会中断
Promise.race(可迭代对象)
- 和all基本相同,只是返回的新期约只与第一个落定的期约相关
async/await
底层依然是promise
async
用于声明函数为异步函数,其实如果异步函数没有await和返回值,则其与同步函数无异
- 函数依然是同步执行
- 返回的是使用Promise.resolve(返回值)包装的期约
- 如果抛出错误,则会返回拒绝的期约并抛出异步错误
await
拥有暂停和恢复执行该异步函数的能力
- await后,会暂停异步函数的执行,将后续代码放入循环机制中,等到后面的Promise为兑现时,再将后续代码放入微任务队列中
- 如果 Promise 被拒绝(
reject),抛出同步异常 - 如果落定为解决的期约,则会获得其值
- 只能在异步函数中使用
async function foo(){
console.log(2);
console.log(await 5);
console.log(6);
}
async function bar(){
console.log(3)
console.log(await Promise.resolve(7));
console.log(8);
}
console.log(1);
foo();
bar();
console.log(4);
await 的工作流程:
- 当
await遇到一个 Promise:- 暂停当前异步函数,将后续代码放入微任务队列。
- 主线程继续执行其他同步代码。
- Promise 的状态变化:
- 如果该 Promise 的状态变为
fulfilled:await捕获到resolve中的值。- 将该值作为
await表达式的结果返回。
- 如果该 Promise 的状态变为
rejected:- 抛出
reject的原因作为同步异常。
- 抛出
- 如果该 Promise 的状态变为
- 恢复异步函数:
- 等待 Promise 解决后,重新进入暂停的函数,继续执行后续代码。
async function example() {
try {
const promise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Oops")), 1000);
});
const result = await promise; // 暂停,等待promise拒绝,并抛出同步错误
} catch (err) {
console.log(err);
}
}
example(); // 输出 Error: Oops