1. 宏任务和微任务
- js 是单线程语言,自始至终只有一个线程在执行 js 脚本
- js 异步执行,是通过事件循环方式实现
1.1 执行过程
- 宏任务执行
- 执行中在遇到异步任务或微任务, 分别进入宏任务队列和微任务队列
- 如果遇到待执行的微任务或任务队列则出队列,则在当前任务执行完毕后, 进入下一tick,并执行。
- 在当前宏任务执行完成后触发 tick 进入下一轮待执行的宏任务微任务, jobs(官方叫法),task( 官方叫法 ) ,直到所有的任务执行完成
1.2 图解
1.3 实例
1.3.1 promise
then, catch
- 一般情况将 catch 放在链式调用的最后捕获异常,捕获 reject, throw, js 语法报错等,做统一处理。
- then 和 catch 的执行,需要看上一个链式执行的结果,then、catch 的回调函数,默认将返回值使用 Promise.resolve() 包裹,只要前一链式调用,无返回值,默认返回 undefined,当然异常除外。
- 在上一链式中抛异常 或 Promise.reject 等可以阻断 then 执行。
- 抛出异常后,异常后面的 .then 被阻塞, 直到遇到 catch 将异常捕获
const p = new Promise( ( resolve, reject ) => {
resolve(1)
});
p.then( res => {
const a = 10;
const b = 12;
console.log( res, 'then1' );
throw 'error';
// return Promise.reject(2);
}).then( res => {
console.log( res, 'then2' );
}).catch( res => {
console.log( res, 'catch1');
// return 3
}).then( res => {
console.log( res, 'then3' );
}).catch( (res) => {
console.log( res, 'catch2' );
})
console.log(1);
setTimeout(() => {
console.log(2);
}, 0 );
const p1 = new Promise((resolve, reject)=> {
console.log(3);
resolve(4)
});
p1.then( res => {
console.log( res );
});
1.4 宏任务
- 代码在执行过程中 如遇到 script、setTimeout、setInterval、I/O、UI 交互事件、 postMessage、MessageChannel、setImmediate(Node.js 环境)等被认为是宏任务标识。
- 以 setTimeout 为例, js 在执行中 遇到 setTimeout 进入宏任务队列(task)、并在代码执行完后,执行队列代码 。
- 如果在 第一次 eventloop 中遇到微任务执行微任务之后执行宏任务。
// 宏任务 setTimout 为例
setTimeout(function a1() {
console.log(1);
}, 300 );
console.log(2);
setTimeout( function a2() {
console.log(3);
}, 2000 );
setTimeout( function a3() {
console.log(4);
}, 1500 );
setTimeout(function a4() {
console.log(5);
}, 500 );
setTimeout( function a5(){
console.log(8);
}, 300 );
console.log( 9 );
1.5 微任务
- 代码中遇到 Promise.then、Object.observe、Mutation、Observer、process.nextTick( Node.js 环境 ),被标识为微任务。
- 微任务和宏任务同级时,均先执行微任务(按照微任务先后顺序执行),后执行宏任务队列。
debugger
console.log(0);
const p = new Promise( resolve => {
resolve(1)
});
setTimeout( () => {
console.log(3);
})
p.then( function p1(res) {
console.log( res );
})
Promise.resolve().then( function p2() {
console.log(4);
})
console.log(2);
// 0, 2, 1, 4, 3
console.log(1);
setTimeout( function se1(){
console.log(2);
const p = new Promise( resolve => {
console.log(3);
resolve(4)
})
console.log(5);
p.then( function p1( res ) {
console.log( res );
})
});
console.log(6);
// 1, 6, 2, 3, 5, 4
console.log(0);
const p = new Promise( resolve => {
console.log( 1 );
resolve(2)
console.log( 3 );
});
p.then( res => {
console.log( res, 'then' );
});
console.log( 4 );
// 0, 1, 3, 4, 2
✋ 详细说明:代码加载到内存中,进入执行环境中。
- 首先执行 console.log(0), 打印 0
- 执行 new Promise,执行 console.log( 1 ); 打印 1
- 执行 new Promise 的 resolve 方法,然后执行 console.log( 3 ); 打印 3 1. 然后执行 p.then 进入微任务队列, 之后执行 console.log( 4 ), 打印 4, 第一轮结束
- 第二轮开始, 执行微任务队列, 取出 p.then 并执行, 执行完成后清空队列。打印结果, 至此所有的任务都执行完毕。
console.log(1);
const fn = () => {
console.log(2);
const p1 = new Promise((resolve, reject) => {
console.log(3);
const p2 = new Promise( (res, rej) => {
console.log(4);
setTimeout( () => {
console.log( 6 );
res(5)
}, 100 )
}).then( () => {
console.log(5)
})
resolve()
});
return p1
}
fn().then( () => {
console.log(8);
});
console.log( 7 );
// result: 1, 2, 3, 4, 7, 8, 6, 5
✋ 详细说明:代码加载到内存中,进入执行环境中。
- 首先打印 1。
- 接着执行 fn, 从上到下一次执行,打印 2
- 进入第一个 new Promise,依然是宏任务,打印 3
- 进入第二个 new Promise,依然是宏任务,打印 4, 然后执行到了 setTimeout, 进入宏任务队列。 1. 然后注意 第二个 new Promise 后的 then,这个函数还有注册,需要等待, 第二个 new Promise 触发 resolve 后,then 才进入微任务队列。
- 然后执行第一个 new Promise 的 resolve,然后 fn().then 进入微任务队列
- 最后执行 console.log( 7 ); 第一个 tick 结束, 进入下一轮。
- 首先检查微任务队列是否有任务待执行,发现有 fn().then, 从微任务队列取出到执行环境中执行,打印 console.log( 8 ); 执行完成后,清空队列。
- 然后检查是否有待执行的宏任务,发现有 setTimeout, 然后把宏任务取出到执行环境执行,console.log( 6 ); 执行到 第二个 new Promise 的 resolve 时,第二个 new Promise().then 进入微任务队列。本轮执行完成,清空宏任务队列,并进入下一个 tick。
- 检查微任务队列是否有待执行的任务,有 第二个 new Promise().then,取出执行,执行完毕后清空队列。并一直循环下去,直到所有的任务执行完成。
💡 在 eventloop 的时候, 注意宏任务或微任务是否已经注册,比如,如果一个 promise, 没有 resolve 则 .then 这个微任务就没有注册,没有注册就谈不上进入微任务队列,宏任务也是一样。在每一次任务执行完成后任务队列会被清空,为下一个 tick 的任务执行腾出空间。
1.6 应用
setTimeout
function hd(){
for( let i = 0; i < num; i++ ){
count += num--;
}
console.log( count );
}
let num = 987654321;
let count = 0;
hd();
改造:
function hd(){
for( let i = 0; i < 10000; i++ ){
if( num < 0 ){
break;
}
count += num--;
}
if( num > 0 ){
setTimeout( hd )
}
console.log( count );
}
let num = 987654321;
let count = 0;
hd();
相关链接
jQuery jQuery.Deferred() 方法 | 菜鸟教程