js事件循环:
由于js是单线程的,同一时间只能干一件事情,当期宿主环境为浏览器时,若一个任务耗时过长会导致页面阻塞。因此有了js事件循环机制,它将任务分成同步任务和异步任务,同步任务在主线程不断执行,异步任务进入任务队列,当同步任务执行完主栈为空时,就去任务队列读取异步任务执行,这个不断循环的过程称为事件循环。
1.promise:
微任务,进入微任务队列,在本轮事件循环中执行,例如:
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
new Promise立即执行,then函数分发到微任务Event Queue。
需要注意的几点
1.promise.then用于指定promise对象成功或者失败的回调函数,所以仅当promise对象为fulfilled或者rejected状态才会去执行,否则不执行:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = promise1.then(() => {
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
执行结果是:
对于这里为什么throw错误之后代码还能继续执行:因为Promise对象和Promise.then方法内部其实自带了try catch,当同步代码发生运行时错误时,会自动将错误对象作为值reject,捕获错误,所以代码还能继续往下运行。此题promise1.then执行后返回一个被reject的promise对象。
2.Promise.then返回值是一个promise对象,以实现链式调用。
(1)当回调函数返回值是一个promise对象时,得到新的promise值作为返回的promise结果值;
(2)若返回值是一个一般值时,将这个值作为返回的promise结果值,相当于resolve(x),可继续向下一个then中的回调去传递;
(3)若不显式的 return 则会 return 一个以 undefined 值 resolve 的 Promise 对象
Promise.resolve(1)
.then((res) => {
console.log(res)
return 2
})
.catch((err) => {
return 3
})
.then((res) => {
console.log('执行了');
console.log(res)
})
依次输出:1,执行了,2
3.promise有pending,fulfilled,rejected三种状态,状态只能被改变一次
const promise = new Promise((resolve, reject) => {
resolve('success1')
reject('error')
resolve('success2')
})
promise
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
代码输出:success1,then:success1
2.setTimeout:
宏任务,进入宏任务队列,在下一轮事件循环执行,例如:
setTimeout(() => {
task();
},3000)
console.log('执行console');
//执行console
//task()
3.async/await:
async声明异步函数,返回的是promise对象,如果返回的不是promise会自动用Promise.resolve()包装,例如:
async function test(){
return 'test'
}
test()
返回值为Promise{<resolved>:"test"}
await表示等待右侧表达式的结果,这个结果是promise对象或者其他值。如果是promise对象则等到的结果为Promise状态变为fulfilled之后resolve的参数,如果不是promise对象则直接将该值作为等到的值。
function test(){
return new Promise(resolve=>{
setTimeout(()=>resolve('test'),2000);
});
}
const result=await test();
console.log(result);
console.log('end');
console.log('end')会等到两秒之后执行
由于await会阻塞后面的代码,所以为了避免造成阻塞,await 必须用在 async 函数中,async 函数调用不会造成阻塞。
function test(){
return new Promise(resolve=>{
setTimeout(()=>resolve('test'),2000);
});
}
async function test2(){
const result=await test();
console.log(result);
}
test2();
console.log('end');
先执行console.log('end'),两秒后执行console.log('test')
async/await的执行顺序:await是一个让出线程的标志,await后面的函数会先执行一遍,然后就会跳出整个async函数(这里相当于从右向左执行,先执行await后面的东西,碰到await就让出线程阻塞代码),先执行async外面的同步代码,同步代码执行完,再回到async内部,继续执行await后面的代码。
async function test1 () {
console.log ('start test1') ;
console.log ( await test2 () ) ;
console.log ('end test1') ;
}
async function test2 () {
console.log ('test2') ;
return await 'return test2 value'
}
test1 () ;
console.log ('start async') ;
setTimeout ( () => {
console.log ('setTimeout') ;
}, 0) ;
new Promise ( (resolve, reject) => {
console.log ('promise1') ;
resolve () ;
}) .then ( () =>{
console.log ('promise2') ;
}) ;
console.log ('end async') ;
上述代码输出:
1.首先执行宏任务,执行test1函数,console.log('start test1');遇到await,执行console.log('test2')
2.执行console.log('start async');
3.遇到setTimeout进入宏任务队列
4.执行new Promise,打印promise1,.then进入微任务队列
5.执行console.log('end async');
6.test1()外面的同步代码执行完毕,回到test1(),执行完console.log(await test2())返回Promise{<resolved>:'return test2 value'},推入微任务队列
7.此时第一个宏任务结束,执行所有的微任务,因为微任务队列先进先出,所以先执行console.log('promise2'),后执行console.log('return test2 value')
8.执行test2完成后,后面的代码不再阻塞,执行console.log('end test1');
9.执行下个宏任务,即执行console.log('setTimeout');
补充:如果去掉test2中return 后面的await,则顺序变为
因为return后面的await阻塞了promise的执行,此时test2()的返回值是一个pending状态的promise,promise只有在resolve或者reject之后才会进入.then中执行回调,因此test2中返回的promise.then在new Promise.then之后进入微任务队列。