宏任务和微任务(逐步分析详细注解)

202 阅读4分钟

1. 宏任务和微任务

  1. js 是单线程语言,自始至终只有一个线程在执行 js 脚本
  2. js 异步执行,是通过事件循环方式实现

1.1 执行过程

  1. 宏任务执行
  2. 执行中在遇到异步任务或微任务, 分别进入宏任务队列和微任务队列
  3. 如果遇到待执行的微任务或任务队列则出队列,则在当前任务执行完毕后, 进入下一tick,并执行。
  4. 在当前宏任务执行完成后触发 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

✋ 详细说明:代码加载到内存中,进入执行环境中。

  1. 首先执行 console.log(0), 打印 0
  2. 执行 new Promise,执行 console.log( 1 ); 打印 1
  3. 执行 new Promise 的 resolve 方法,然后执行 console.log( 3 ); 打印 3 1. 然后执行 p.then 进入微任务队列, 之后执行 console.log( 4 ), 打印 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. 首先打印 1。
  2. 接着执行 fn, 从上到下一次执行,打印 2
  3. 进入第一个 new Promise,依然是宏任务,打印 3
  4. 进入第二个 new Promise,依然是宏任务,打印 4, 然后执行到了 setTimeout, 进入宏任务队列。 1. 然后注意 第二个 new Promise 后的 then,这个函数还有注册,需要等待, 第二个 new Promise 触发 resolve 后,then 才进入微任务队列。
  5. 然后执行第一个 new Promise 的 resolve,然后 fn().then 进入微任务队列
  6. 最后执行 console.log( 7 ); 第一个 tick 结束, 进入下一轮。
  7. 首先检查微任务队列是否有任务待执行,发现有 fn().then, 从微任务队列取出到执行环境中执行,打印 console.log( 8 ); 执行完成后,清空队列。
  8. 然后检查是否有待执行的宏任务,发现有 setTimeout, 然后把宏任务取出到执行环境执行,console.log( 6 ); 执行到 第二个 new Promise 的 resolve 时,第二个 new Promise().then 进入微任务队列。本轮执行完成,清空宏任务队列,并进入下一个 tick。
  9. 检查微任务队列是否有待执行的任务,有 第二个 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();

相关链接

Proxy - JavaScript | MDN

Promise - JavaScript | MDN

ES6 入门教程

js中的宏任务与微任务

jQuery jQuery.Deferred() 方法 | 菜鸟教程

视频

微任务宏任务.mp4