搞懂javascript代码执行机制

133 阅读7分钟

同步异步

记住js是单线程,因为这个原因会遇到一些问题,例如事情得一个做完才能开始下一个,但是很多场景是需要同时进行的,因此js自己模拟了多线程,怎么做的呢?

聪明的程序员将任务分为两类:

  • 同步任务
  • 异步任务

image.png

图要表达的内容用文字来表述的话:

  • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

那怎么知道主线程执行栈为空啊?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

上代码说明

题1-带分析过程

let data = [];
$.ajax({
    url:www.javascript.com,
    data:data,
    success:() => {
        console.log('发送成功!');
    }
})
console.log('代码执行结束');

代码分析:

  • ajax进入Event Table,注册回调函数success
  • 执行console.log('代码执行结束')
  • ajax事件完成,回调函数success进入Event Queue。
  • 主线程从Event Queue读取回调函数success并执行。

setInterval

setTimeout,当然不能错过它的孪生兄弟setInterval。他俩差不多,只不过后者是循环的执行。对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需 要等待。 唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。一旦**setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了**

setTimeout

题1

setTimeout(() => {
    console.log('延时3秒');
},3000)

大家对他的第一印象就是异步可以延时执行,我们经常这么实现延时3秒执行 修改一下

题2-带分析过程

setTimeout(() => { task() },3000) 
sleep(10000000)

这个能保证一定是在3秒执行的吗? 分析一下这个代码

  • task()进入Event Table并注册,计时开始。
  • 执行sleep函数,很慢,非常慢,计时仍在继续。
  • 3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep也太慢了吧,还没执行完,只好等着。
  • sleep终于执行完了,task()终于从Event Queue进入了主线程执行。 上述的流程走完,我们知道setTimeout这个函数,是经过指定时间后,把要执行的任务(本例中为task())加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。

我们还经常遇到setTimeout(fn,0)这样的代码,0秒后执行又是什么意思呢?是不是可以立即执行呢?

答案是不会的,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。举例说明:

题3

//代码1
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
},0);

//先执行这里
//执行啦
//代码2
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
},3000);
//先执行这里
// ... 3s later
// 执行啦

题4


function fn(){
    console.log('start');
    setTimeout(()=>{
        console.log('setTimeout');
    },0);
    console.log('end');
}

fn() 

// 输出 start end setTimeout

Promise

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick

题1-带分析过程

setTimeout(function() {
    console.log('setTimeout');
})

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');

结果:
promise
console
setTimeout

分析:

第一轮事件循环: 从上往下一行行执行

  • setTimeout 将其回调函数注册后分发到宏任务Event Queue
  • Promisenew Promise立即执行,then函数分发到微任务Event Queue,因为promise执行里没有resolve,微任务里没有内容了
  • 遇到console.log('console');,立即执行

第一次事件循环结束

第二次事件循环开始 宏任务里有setTimeout的回调函数执行

题2-带分析过程

console.log('1');

setTimeout(function() {
    console.log('2');
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
结果:
1
7
8
2
4
5
9
11
12

分析: 第一轮事件循环:

  • 先执行console.log('1');
  • 遇到setTimeout 将其回调函数注册后分发到宏任务Event Queue 记为setTimeout1
  • new Promise 立即执行 console.log('7'); then函数分发到微任务Event Queue,因为promise执行里执行了resolve,所以then记录到了微任务里then1 这里只先执行结果7
  • setTimeout 将其回调函数注册后分发到宏任务Event Queue 记为setTimeout2

image.png 第一轮事件循环结束 1 7 检查是否有微任务,如果有微任务需要把所有记录的微任务都执行完后清空队列开始准备下一次的循环 因此这里执行了 8 最张结果是 1 7 8

第二轮事件循环从宏任务开始在第一轮的时候宏任务里记录了我们从setTimeout1开始:

  • 宏任务setTimeout1 console.log('2')
  • 遇到new promise 立即执行4 then放到微任务里记录then1
  • 第二个setTimeout2 记录宏任务里等下次执行 等记录完后看记录里有微任务执行then1 结果2 4 5

image.png

第三轮开始;setTimeout2

  • 宏任务setTimeout2 console.log('9')
  • 遇到new promise 立即执行11 then放到微任务里记录then2 等记录完看微任务执行then2 结果 12

image.png

题3

我们再来修改一下上面的例子

console.log('1');

setTimeout(function() {
    console.log('2');
    new Promise(function(resolve) {
        console.log('4');
        //resolve();
    }).then(function() {
        console.log('5')
    })
})
new Promise(function(resolve) {
    console.log('7');
    //resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
结果:
1
7
2
4
9
11
12
分析就不写了

题4-带分析过程

setTimeout(function(){
 console.log('定时器开始啦')
});

new Promise(function(resolve){
 console.log('马上执行for循环啦');
 for(var i = 0; i < 10000; i++){
  i == 99 && resolve();
 }
}).then(function(){
 console.log('执行then函数啦')
});

console.log('代码执行结束');

结果:
马上执行for循环啦
代码执行结束
执行then函数啦
定时器开始啦

分析:

第一次轮循:

  • setTimeout 放入宏任务记录setTimeout1 下一次轮循执行
  • new promise立即执行 打印出 马上执行for循环啦
  • 符合条件下执行了resolve() 记录then到微任务 then1
  • 执行console.log('代码执行结束') 记录完后,看微任务里有一个记录then1执行 第一次结束: 结果 :
马上执行for循环啦 
代码执行结束
执行then函数啦

image.png

第二次轮循:

里面只有宏任务里的setTimeout1了

  • 宏任务里的setTimeout1开始 console.log定时器开始啦

题5-带分析过程

new Promise((resolve) => {
	console.log(1)
  resolve()
}).then(()=>{
	console.log(2)
})
console.log(3)
// 上面代码输出1 3 2
function test() {
  console.log(1)
  setTimeout(function () { 	// timer1
    console.log(2)
  }, 1000)
}

test();

setTimeout(function () { 		// timer2
  console.log(3)
})

new Promise(function (resolve) {
  console.log(4)
  setTimeout(function () { 	// timer3
    console.log(5)
  }, 100)
  resolve()
}).then(function () {
  setTimeout(function () { 	// timer4
    console.log(6)
  }, 0)
  console.log(7)
})

console.log(8)

// 输出1,4,8,7,3,6,5,2

分析:

  1. JS是顺序从上而下执行;
  2. 执行到test(),test方法为同步,直接执行console.log(1)打印1;
  3. test方法中setTimeout为异步宏任务,回调我们把它记做timer1放入宏任务队列;
  4. test方法下面有一个setTimeout为异步宏任务,回调我们把它记做timer2放入宏任务队列;
  5. 执行promise,new Promise是同步任务,直接执行,打印4;
  6. new Promise里面的setTimeout是异步宏任务,回调我们记做timer3放到宏任务队列;
  7. Promise.then是微任务,放到微任务队列;
  8. console.log(8)是同步任务,直接执行,打印8;
  9. 主线程任务执行完毕,检查微任务队列中有Promise.then
  10. 开始执行微任务,发现有setTimeout是异步宏任务,记做timer4放到宏任务队列;
  11. 微任务队列中的console.log(7)是同步任务,直接执行,打印7;
  12. 微任务执行完毕,第一次循环结束;
  13. 检查宏任务队列,里面有timer1、timer2、timer3、timer4,四个定时器宏任务,按照定时器延迟时间得到可以执行的顺序,即Event Queue:timer2、timer4、timer3、timer1,依次拿出放入执行栈末尾执行;
  14. 执行timer2,console.log(3)为同步任务,直接执行,打印3;
  15. 检查没有微任务,第二次Event Loop结束;
  16. 执行timer4,console.log(6)为同步任务,直接执行,打印6;
  17. 检查没有微任务,第三次Event Loop结束;
  18. 执行timer3,console.log(5)同步任务,直接执行,打印5;
  19. 检查没有微任务,第四次Event Loop结束;
  20. 执行timer1,console.log(2)同步任务,直接执行,打印2;
  21. 检查没有微任务,也没有宏任务,第五次Event Loop结束;

image.png

题6

function fn() {
    setTimeout(()=>{
        console.log('a');
    },0);
    new Promise((resolve)=>{
        console.log('b');
        resolve();
    }).then(()=>{
        console.log('c')
    });
}
fn() 

结果:
// b c a


async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
async1();

结果:
//async1 start
//async2
//async1 end

题7

async function async1() {
  console.log('1');
  await async2();
  console.log('2');
}
 
async function async2() {
  console.log('3');
}
 
console.log('4');
 
setTimeout(function() {
    console.log('5');
}, 0);  
 
async1();
 
new Promise(function(resolve) {
    console.log('6');
    resolve();
  }).then(function() {
    console.log('7');
});
 
console.log('8');
结果:
4 1 3 6 8 2 7 5

题8

setTimeout(function () {
  console.log(1);
}, 100);

new Promise(function (resolve) {
  console.log(2);
  resolve();
  console.log(3);
}).then(function () {
  console.log(4);
  new Promise((resove, reject) => {
    console.log(5);
    setTimeout(() =>  {
      console.log(6);
    }, 10);
  })
});
console.log(7);
console.log(8);
结果:
2 3 7 8 4 5 6 1

题9

setTimeout(function () {
  console.log(1);
}, 100);
async function async2() {
    console.log('async2');
}
new Promise(async function (resolve) {
  console.log(2);
  resolve();
    await async2();
    
  console.log(3);
}).then(function () {
  console.log(4);
  new Promise((resove, reject) => {
    console.log(5);
    setTimeout(() =>  {
      console.log(6);
    }, 10);
  })
});
console.log(7);
console.log(8);
结果:
2 async2  7 8 3 4 5 6 1

题10

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)
结果:

promise1 Promise { <pending> }
promise2 Promise { <pending> }
(node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!
(node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
promise1 Promise { 'success' }
promise2 Promise {
  <rejected> Error: error!!!
    at promise.then (...)
    at <anonymous> }

题11

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)
  })
  
  //then:  success1
  
分析:构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用,呼应代码二结论:promise 状态一旦改变则不能再变。

题12

Promise.resolve(1)
.then((res) => {
  console.log(res)
  return 2
})
.catch((err) => {
  return 3
})
.then((res) => {
  console.log(res)
})
// 1 2

题13

process.nextTick(() => {
console.log('nextTick')
})
Promise.resolve()
.then(() => {
  console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')

结果:
end
nextTick
then
setImmediate

分析:
`process.nextTick``promise.then` 都属于 microtask,而 `setImmediate` 属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask

promise中的then、catch、finally

题1

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);
结果:
1
2
4
"timerStart"
"timerEnd"
"success"

题2-带分析过程

Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');
结果:
'start'
'promise1'
'timer1'
'promise2'
'timer2'

分析:

  • 刚开始整个脚本作为第一次宏任务来执行,我们将它标记为宏1,从上至下执行
  • 遇到Promise.resolve().then这个微任务,将then中的内容加入第一次的微任务队列标记为微1
  • 遇到定时器timer1,将它加入下一次宏任务的延迟列表,标记为宏2,等待执行(先不管里面是什么内容)
  • 执行宏1中的同步代码start
  • 第一次宏任务(宏1)执行完毕,检查第一次的微任务队列(微1),发现有一个promise.then这个微任务需要执行
  • 执行打印出微1中同步代码promise1,然后发现定时器timer2,将它加入宏2的后面,标记为宏3
  • 第一次微任务队列(微1)执行完毕,执行第二次宏任务(宏2),首先执行同步代码timer1
  • 然后遇到了promise2这个微任务,将它加入此次循环的微任务队列,标记为微2
  • 宏2中没有同步代码可执行了,查找本次循环的微任务队列(微2),发现了promise2,执行它
  • 第二轮执行完毕,执行宏3,打印出timer2

image.png

题3

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)

结果:
'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}

分析:

  • 从上至下,先执行第一个new Promise中的函数,碰到setTimeout将它加入下一个宏任务列表
  • 跳出new Promise,碰到promise1.then这个微任务,但其状态还是为pending,这里理解为先不执行
  • promise2是一个新的状态为pendingPromise
  • 执行同步代码console.log('promise1'),且打印出的promise1的状态为pending
  • 执行同步代码console.log('promise2'),且打印出的promise2的状态为pending
  • 碰到第二个定时器,将其放入下一个宏任务列表
  • 第一轮宏任务执行结束,并且没有微任务需要执行,因此执行第二轮宏任务
  • 先执行第一个定时器里的内容,将promise1的状态改为resolved且保存结果并将之前的promise1.then推入微任务队列
  • 该定时器中没有其它的同步代码可执行,因此执行本轮的微任务队列,也就是promise1.then,它抛出了一个错误,且将promise2的状态设置为了rejected
  • 第一个定时器执行完毕,开始执行第二个定时器中的内容
  • 打印出'promise1',且此时promise1的状态为resolved
  • 打印出'promise2',且此时promise2的状态为rejected

题4

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)
结果:
'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}
  • 从上至下,先执行第一个new Promise中的函数,碰到setTimeout将它加入下一个宏任务列表
  • 跳出new Promise,碰到promise1.then这个微任务,但其状态还是为pending,这里理解为先不执行
  • promise2是一个新的状态为pendingPromise
  • 执行同步代码console.log('promise1'),且打印出的promise1的状态为pending
  • 执行同步代码console.log('promise2'),且打印出的promise2的状态为pending
  • 碰到第二个定时器,将其放入下一个宏任务列表
  • 第一轮宏任务执行结束,并且没有微任务需要执行,因此执行第二轮宏任务
  • 先执行第一个定时器里的内容,将promise1的状态改为resolved且保存结果并将之前的promise1.then推入微任务队列
  • 该定时器中没有其它的同步代码可执行,因此执行本轮的微任务队列,也就是promise1.then,它抛出了一个错误,且将promise2的状态设置为了rejected
  • 第一个定时器执行完毕,开始执行第二个定时器中的内容
  • 打印出'promise1',且此时promise1的状态为resolved
  • 打印出'promise2',且此时promise2的状态为rejected

题5

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);
  })
  结果:
  then:success1

分析:

  • new promise立即执行状态只执行第一次的且不可改变,里面没有打印内容,只是记录了promise的状态
  • then 记录微任务执行
const promise = new Promise((resolve, reject) => {
  reject("error");
  resolve("success2");
});
promise
.then(res => {
    console.log("then1: ", res);
  }).then(res => {
    console.log("then2: ", res);
  }).catch(err => {
    console.log("catch: ", err);
  }).then(res => {
    console.log("then3: ", res);
  })
结果:
"catch: " "error"
"then3: " undefined

验证了第三个结论,catch不管被连接到哪里,都能捕获上层未捕捉过的错误。

至于then3也会被执行,那是因为catch()也会返回一个Promise,且由于这个Promise没有返回值,所以打印出来的是undefined

题6

Promise.resolve(1)
  .then(res => {
    console.log(res);
    return 2;  //`return 2`会被包装成`resolve(2)`
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res);
  });
  
  结果: 1 2
  
  
  Promise.reject(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    console.log(err);
    return 3
  })
  .then(res => {
    console.log(res);
  });
  结果: 1  3

.then函数中的两个参数。 第一个参数是用来处理Promise成功的函数,第二个则是处理失败的函数。 也就是说Promise.resolve('1')的值会进入成功的函数,Promise.reject('2')的值会进入失败的函数。

题7

Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }, (err) => {
    console.log('error', err)
  }).catch(err => {
    console.log('catch', err)
  })
  //'error' 'error!!!'

题8

Promise.reject('error!!!')
.then((res) => {
  console.log('success', res)
}).catch(err => {
  console.log('catch', err)
})
//'catch' 'error!!!'

Promise.resolve()
.then(function success (res) {
  throw new Error('error!!!')
}, function fail1 (err) {
  console.log('fail1', err)
}).catch(function fail2 (err) {
  console.log('fail2', err)
})
//fail2 Error: error!!!
  		at success

finally()

  • .finally()方法不管Promise对象最后的状态如何都会执行
  • .finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected
  • 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。

题9

function promise1 () {
  let p = new Promise((resolve) => {
    console.log('promise1');
    resolve('1')
  })
  return p;
}
function promise2 () {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}
promise1()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally1'))

promise2()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally2'))
  
 结果:
 'promise1'
'1'
'error'
'finally1'
'finally2'

执行过程:

  • 首先定义了两个函数promise1promise2,先不管接着往下看。
  • promise1函数先被调用了,然后执行里面new Promise的同步代码打印出promise1
  • 之后遇到了resolve(1),将p的状态改为了resolved并将结果保存下来。
  • 此时promise1内的函数内容已经执行完了,跳出该函数
  • 碰到了promise1().then(),由于promise1的状态已经发生了改变且为resolved因此将promise1().then()这条微任务加入本轮的微任务列表(这是第一个微任务)
  • 这时候要注意了,代码并不会接着往链式调用的下面走,也就是不会先将.finally加入微任务列表,那是因为.then本身就是一个微任务,它链式后面的内容必须得等当前这个微任务执行完才会执行,因此这里我们先不管.finally()
  • 再往下走碰到了promise2()函数,其中返回的new Promise中并没有同步代码需要执行,所以执行reject('error')的时候将promise2函数中的Promise的状态变为了rejected
  • 跳出promise2函数,遇到了promise2().catch(),将其加入当前的微任务队列(这是第二个微任务),且链式调用后面的内容得等该任务执行完后才执行,和.then()一样。
  • OK, 本轮的宏任务全部执行完了,来看看微任务列表,存在promise1().then(),执行它,打印出1,然后遇到了.finally()这个微任务将它加入微任务列表(这是第三个微任务)等待执行
  • 再执行promise2().catch()打印出error,执行完后将finally2加入微任务加入微任务列表(这是第四个微任务)
  • OK, 本轮又全部执行完了,但是微任务列表还有两个新的微任务没有执行完,因此依次执行finally1finally2

就像是这里的finally()会等promise1().then()执行完才会将finally()加入微任务队列,其实如果这道题中你把finally()换成是then()也是这样的

function promise1() {
  let p = new Promise((resolve) => {
    console.log('promise1');
    resolve('1')
  })
  return p;
}
function promise2() {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}
promise1()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .then(() => console.log('finally1'))

promise2()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .then(() => console.log('finally2'))
  
  //结果:
 promise1
 1
 error
 finally1
 finally2

image.png

function promise1 () {
  let p = new Promise((resolve) => {
    console.log('promise1');
    resolve('1')
  })
  return p;
}
function promise2 () {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}
promise1()
    .finally(() => console.log('finally1'))
  .then(res => console.log(res))
  .catch(err => console.log(err))
  

promise2()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally2'))
结果:
promise1
VM299:14 finally1
VM299:21 error
VM299:22 finally2
VM299:15 1
function promise1 () {
  let p = new Promise((resolve) => {
    console.log('promise1');
    resolve('1')
  })
  return p;
}
function promise2 () {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}
promise1()
    .finally(() => console.log('finally1'))
    .then(res => console.log('打印的是我吗'+res))
    .finally(() => console.log('finally3'))
  .then(res => console.log('这个呢?==='+res))
  .catch(err => console.log(err))
  

promise2()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally2'))
    .then(res => console.log(res+'1111'))
    
  //结果:
promise1
finally1
error
finally2
打印的是我吗1
finally3
undefined1111
这个呢?===undefined

image.png

function promise1 () {
  let p = new Promise((resolve,reject) => {
    console.log('promise1');
    //resolve('1')
      reject(1)
  })
  return p;
}
function promise2 () {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}
promise1()
    .finally(() => console.log('finally1'))
    .then(res => console.log('打印的是我吗'+res))
    .finally(() => console.log('finally3'))
    .catch(err => console.log('我是catch'+err))
  .then(res => console.log('这个呢?==='+res))
  
  

promise2()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally2'))
    .then(res => console.log(res+'1111'))
    
 结果:
promise1
finally1
error
finally2
finally3
undefined1111
我是catch1
这个呢?===undefined

image.png

总结:

promise执行过程中,如果遇到then会先记入微任务中,后面的then就先不管,因为代码并不会接着往链式调用的下面走,也就是不会先将后面.then加入微任务列表,那是因为.then本身就是一个微任务,它链式后面的内容必须得等当前这个微任务执行完才会执行,因此这里我们先不管链式后面的 可以参考题9

Promise中的all和race

这里的内容我就直接搬来 【LinDaiDai_霖呆呆】大佬的题 在做下面👇的题目之前,让我们先来了解一下Promise.all()Promise.race()的用法。

通俗来说,.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。

.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。

来看看题目一 我们知道如果直接在脚本文件中定义一个Promise,它构造函数的第一个参数是会立即执行的,就像这样:

const p1 = new Promise(r => console.log('立即打印'))

控制台中会立即打印出 “立即打印”。

因此为了控制它什么时候执行,我们可以用一个函数包裹着它,在需要它执行的时候,调用这个函数就可以了:

function runP1 () {
    const p1 = new Promise(r => console.log('立即打印'))
    return p1
}

runP1() // 调用此函数时才执行

题1

现在来构建这么一个函数:

function runAsync (x) {
    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
    return p
}

该函数传入一个值x,然后间隔一秒后打印出这个x 如果我用.all()来执行它会怎样呢?

function runAsync (x) {
    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
    return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log(res))
  
1 2 3 [1, 2, 3]

所以你现在能理解这句话的意思了吗:有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。

.all()后面的.then()里的回调函数接收的就是所有异步操作的结果。

而且这个结果中数组的顺序和Promise.all()接收到的数组顺序一致!!!

适合的场景

有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。

题2

我新增了一个runReject函数,它用来在1000 * x秒后reject一个错误。

同时.catch()函数能够捕获到.all()里最先的那个异常,并且只执行一次。

想想这道题会怎样执行呢 🤔️?

function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
function runReject (x) {
  const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
  return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
  .then(res => console.log(res))
  .catch(err => console.log(err))
  
结果:
// 1s后输出
1
3
// 2s后输出
2
Error: 2
// 4s后输出
4
//第一段代码
let first_promise = new Promise( (resolve, reject) => {
    resolve();
    console.log( "我是外部的promise,立即执行!");
}).then( res => {
    console.log( "我是外部的promise的第一个then回调!");
    return new Promise( (resolve, reject) => {
        resolve();
        console.log( "我是内部的promise,立即执行!");
    }).then( res => {
        console.log("我是内部的promise的第一个then回调!");
    }).then( res => {
        console.log("我是内部的promise的第二个then回调!");
    })
}).then( res => {
    console.log( "我是外部的promise的第二个then回调!");
})

//打印结果:
我是外部的promise,立即执行!
我是外部的promise的第一个then回调!
我是内部的promise,立即执行!
我是内部的promise的第一个then回调!

我是内部的promise的第二个then回调!
我是外部的promise的第二个then回调!


async await

题1-带分析过程

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

结果:

script start
async1 start
async2
    // async1 end 注意这个自己就弄错了这句应该在
promise1
script end
     async1 end  //正确的是在这里
promise2
setTimeout

分析:

第一轮循环:

  • 微任务里记录async1 async2
  • 宏任务里的打印console.log('script start')
  • setTimeout 放到宏任务 记录-宏 --setTimeout1
  • 遇到先执行async1() 进入里面
  • 打印console.log('async1 start')
  • 执行async2() 进入打印 async2 注意这里后面的代码会被加入到下一次的微任务里记录-微1async1 end`
  • new promise 立即执行 打印console.log('promise1');
  • 记录-微2-then1 console.log('promise2');
  • 打印 console.log(script end)

先把打印都打印出来 :

script start 
async1 start
async2
promise1
script end
然后再检查微任务列表里有没有记录的微任务,找到有微12,开始顺序执行
async1 end
promise2

第一次就结果了

第二轮循环

  • 宏任务里就只有一个记录setTimeout2 打印console.log('setTimeout');

image.png

题2

继续修改一下看看结果吧

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});

async1();


console.log('script end');

结果:
script start
promise1
async1 start
async2
script end
promise2
async1 end
setTimeout

题3

setTimeout(() => console.log(4))

async function test() {
  console.log(1)
  await Promise.resolve()
  console.log(3)
}

test()

console.log(2)
// 输出1 2 3 4

宏任务和微任务共存

    console.log('start');
    let timer1 = setTimeout(() => {
        console.log('timeout');
    }, 0);

    let promise1 = new Promise((resolve, reject) => {
        console.log( "我是promise1" );
        resolve( "我成功了!" );
    });
    promise1.then(val => {
        console.log(val);
        return "我是Promise1中第一个then的返回值!"
    }).then((val) => {
        console.log(val);
        let timer3 = setTimeout(() => {
            console.log('我是Promise1中第二个then中的setTimeout!');
        });
    });
 
    let promise2 = new Promise((resolve, reject) => {
        let timer2 = setTimeout(() => {
            console.log( "我是promise2" );
            resolve();
        });
    })
    promise2.then(() => {
        console.log('我是Promise2中第一个then!');
    }).then(() => {
        console.log('我是Promise2中第二个then!');
    });
    
    console.log('end');
    
start
我是promise1
end
我成功了!
我是Promise1中第一个then的返回值!

timeout

我是promise2
我是Promise2中第一个then!
我是Promise2中第二个then!

我是Promise1中第二个then中的setTimeout

按照上面的分析过程,我们知道当执行主JS代码时,会率先打印出start我是promise1end
此时的宏任务列表中存在的任务分别是:timer1timer2的回调函数,微任务列表中仅存在promise1.then的回调。

当主JS代码执行完成后,会依次执行微任务列表中的任务。

执行promise1.then的回调打印出我成功了!,此时promise1.then()产生的新Promise实例的状态已经确定,因此把promise1.then.then的回调添加到微任务队列中。因为promise1.then的回调已经执行,所以被移出队列, 那么此时微任务列表中仅有这一个微任务,执行他打印出我是Promise1中第二个then!,并存把timer3的回调函数添加到宏任务列表中等待执行,至此微任务列表空了,本轮结束!

下一轮事件循环开始,从宏任务开始,执行timer1的回调打印出timeout,但本轮并没有产生新的的微任务,微任务仍为空,本轮结束。

进入到下一轮,从宏任务开始,执行timer2的回调,打印出我是promise2,此时promise2的状态才确定下来,把promise2.then的回调放到微任务列表中。timer2的回调这个宏任务执行完之后,紧接着依次取出微任务队列中的任务去执行,此时微任务队列中仅有promise2.then的回调这一个任务,执行打印我是Promise2中第一个then!。这个时候Promise2.then()得到Promise实例状态也随之确定了下来,因此会把promise2.then.then的回调添加到微任务列表中,执行打印出我是Promise2中第二个then!。微任务列表再次清空,本轮结束。

下一轮继续,执行宏任务timer3的回调,打印我是Promise1中第二个then中的setTimeout!,没有待执行的微任务,至此宏任务、微任务列表都为空,循环结束!

综合题

题1

console.log('script start')

async function async1() {
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2 end')
}
async1()

setTimeout(function() {
    console.log('setTimeout')
}, 0)

new Promise(resolve => {
    console.log('Promise')
resolve()
})
.then(function() {
    console.log('promise1')
})
.then(function() {
    console.log('promise2')
})

console.log('script end')
 // 旧版输出如下,但是请继续看完本文下面的注意那里,新版有改动
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout

新版本输出
script start => async2 end => Promise => script end => async1 end =>promise1 => promise2 =>  setTimeout

node版本10之后都是按新版本把所有微任务都执行完

题2

console.log('script start')

async function async1() {
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2 end')
    return Promise.resolve().then(()=>{ //这里和题1的不同,导致async1处理后面代码的顺序不同,如果这里是异步代码那async1后面的代码先不记到微任务里
        console.log('async2 end1')
    })
}
async1()

setTimeout(function() {
    console.log('setTimeout')
}, 0)

new Promise(resolve => {
    console.log('Promise')
    resolve()
})
.then(function() {
    console.log('promise1')
})
.then(function() {
    console.log('promise2')
})

console.log('script end')

script start
async2 end
Promise
script end
async2 end1
promise1
promise2
async1 end
setTimeout

总结:

做了一些题后这类题需要注意的点

正常从上往下看,特殊的就是 setTimeout new promise then async await

  • 遇到setTimeout时就记录下一次轮循的宏任务里

  • new promise是立即执行

  • then 我们都知道promise的状态,会由pending(进行中)变成fulfilled(已成功)rejected(已失败),当它的状态确定时(成功\失败),此时就是回调被添加到微任务队列的契机。

  • await 之前的类似new promise,之后的需要放入到微任务里,很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志await后面的表达式会先执行一遍,将await后面的代码加入到microtask中,然后就会跳出整个async函数来执行后面的代码。

  • 执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。举个例子:

image.png

自己的一些理解,如果有问题还望大佬给指出来说明一下,感谢

有记录一些大佬的题 juejin.cn/post/684490…