1 async、await
2 浏览器进程、线程
3 宏任务、微任务队列
4 Promise面试题解析
5 throw、try、catch、finally
// 普通函数 // function foo() {} // const bar = function() {} // const baz = () => {} // 生成器函数 // function* foo() {} // 异步函数 async function foo() { console.log("foo function1") console.log("foo function2") console.log("foo function3") } foo() // const bar = async function() {} // const baz = async () => {} // class Person { // async running() {} // }
// 返回值的区别 // 1.普通函数 // function foo1() { // return 123 // } // foo1() // 2.异步函数 async function foo2() { // 1.返回一个普通的值 // -> Promise.resolve(321) return ["abc", "cba", "nba"] // 2.返回一个Promise // return new Promise((resolve, reject) => { // setTimeout(() => { // resolve("aaa") // }, 3000) // }) // 3.返回一个thenable对象 // return { // then: function(resolve, reject) { // resolve("bbb") // } // } } foo2().then(res => { console.log("res:", res) })
在async里面的代码如果有报错和异常,那么不会影响后续代码,但是结果会放到promise的reject里面传递出来。
// "abc".filter() // 什么情况下异步函数的结果是rejected // 如果异步函数中有抛出异常(产生了错误), 这个异常不会被立即浏览器处理 // 进行如下处理: Promise.reject(error) async function foo() { console.log("---------1") console.log("---------2") // "abc".filter() throw new Error("coderwhy async function error") console.log("---------3") // return new Promise((resolve, reject) => { // reject("err rejected") // }) return 123 } // promise -> pending -> fulfilled/rejected foo().then(res => { console.log("res:", res) }).catch(err => { console.log("coderwhy err:", err) console.log("继续执行其他的逻辑代码") })
await如果单纯用在普通变量和普通函数里面是没什么用的,它是用来配合promise来一起使用的,作用和yield差不多,就是等到promise有返回之后再接收结果然后再执行await下面几行的代码(就是会打断代码执行)。
// 1.普通函数 // function foo1() { // await 123 // } // foo1() // 2.await关键字 // await条件: 必须在异步函数中使用 function bar() { console.log("bar function") return new Promise(resolve => { setTimeout(() => { resolve(123) }, 100000) }) } async function foo() { console.log("-------") // await后续返回一个Promise, 那么会等待Promise有结果之后, 才会继续执行后续的代码 const res1 = await bar() console.log("await后面的代码:", res1) const res2 = await bar() console.log("await后面的代码:", res2) console.log("+++++++") } foo()
在promise返回的是reject的时候,用catch可以捕获异常和错误。或者在getdata里面写try catch。
function requestData(url) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(url) // reject("error message") }, 2000); }) } async function getData() { const res1 = await requestData("why") console.log("res1:", res1) const res2 = await requestData(res1 + "kobe") console.log("res2:", res2) } getData().catch(err => { console.log("err:", err) })
普通函数返回promise、但是异步async关键字创建的函数也一样可以在调用时候使用await。
// 1.定义一些其他的异步函数 function requestData(url) { console.log("request data") return new Promise((resolve) => { setTimeout(() => { resolve(url) }, 3000) }) } async function test() { console.log("test function") return "test" } async function bar() { console.log("bar function") return new Promise((resolve) => { setTimeout(() => { resolve("bar") }, 2000); }) } async function demo() { console.log("demo function") return { then: function(resolve) { resolve("demo") } } } // 2.调用的入口async函数 async function foo() { console.log("foo function") const res1 = await requestData("why") console.log("res1:", res1) const res2 = await test() console.log("res2:", res2) const res3 = await bar() console.log("res3:", res3) const res4 = await demo() console.log("res4:", res4) } foo()
每个浏览器打开的页面都会成为一个进程,一个进程里面会有很多线程,但是执行JavaScript的线程只有一个。
let name = "why" name = "kobe" function bar() { console.log("bar function") } function foo() { console.log("foo function") // 1.在JavaScript内部执行 // let total = 0 // for (let i = 0; i < 1000000; i++) { // total += i // } // 2.创建一个定时器 setTimeout(() => { console.log("setTimeout") }, 10000); bar() } foo()
不管定时器定的时间是多少,都会先执行定时器后面的代码。
事件队列,事件循环。
定时器、dom监听、网络请求都会被加到事件队列里面,然后事件队列按照先进先出的顺序把事件输入到执行上下文中。
promise里面的函数和写在外面的函数一样,都是马上就执行,没有放到事件队列中。但是如果这个promise有执行resolve或者reject时,回调函数then和catch里面的代码是会放到队列里面。
定时器的代码会被放到宏任务(macrotask)中,then里面的代码会放到微任务(microtask)当中。
宏任务和微任务的划分是规定好了的,不是我们来选择的。
微任务的作用就是希望比宏任务先执行,所以微任务比宏任务先执行。
console.log("script start") // function bar() { // console.log("bar function") // } // function foo() { // console.log("foo function") // bar() // } // foo() // 定时器 setTimeout(() => { console.log("setTimeout0") }, 0) setTimeout(() => { console.log("setTimeout1") }, 0) // Promise中的then的回调也会被添加到队列中 console.log("1111111") new Promise((resolve, reject) => { console.log("2222222") console.log("-------1") console.log("-------2") resolve() console.log("-------3") }).then(res => { console.log("then传入的回调: res", res) }) console.log("3333333") console.log("script end")
注意就是普通的函数和promise回调函数都是直接放到执行上下文中执行的,promise的resolve和reject回调的then和catch里面的代码是放到微任务中执行。
微任务比宏任务先执行
最好的办法就是把宏任务队列和微任务队列的执行顺序画出来
微任务如果里面有微任务的话,宏任务就会一直延后才会执行,不管微任务是否早就执行完成。执行到宏任务时如果创建里面微任务(已经执行到宏任务了,只能说微任务已经结束了),等这一个宏任务完成之后,就立马回去执行微任务。
最终执行顺序:
console.log("script start") setTimeout(function () { console.log("setTimeout1"); new Promise(function (resolve) { resolve(); }).then(function () { new Promise(function (resolve) { resolve(); }).then(function () { console.log("then4"); }); console.log("then2"); }); }); new Promise(function (resolve) { console.log("promise1"); resolve(); }).then(function () { console.log("then1"); }); setTimeout(function () { console.log("setTimeout2"); }); console.log(2); queueMicrotask(() => { console.log("queueMicrotask1") }); new Promise(function (resolve) { resolve(); }).then(function () { console.log("then3"); }); console.log("script end")
then如果是在宏任务中执行的,那包裹这个then的宏任务会先执行后再执行then微任务。
定时器还分时间的,时间短的先进宏任务。
await还没拿到结果前,这个作用域内的后续代码直接不执行,跳过。并且在同一个作用域的await后面的代码是相当于then里面的代码,都是放到微任务的。
console.log("script start") function requestData(url) { console.log("requestData") return new Promise((resolve) => { setTimeout(() => { console.log("setTimeout") resolve(url) }, 2000); }) } // 2.await/async async function getData() { console.log("getData start") const res = await requestData("why") console.log("then1-res:", res) console.log("getData end") } getData() console.log("script end") // script start // getData start // requestData // script end // setTimeout // then1-res: why // getData end
下图执行到async2()的时候会去调用asunc2的函数,虽然只打印的是async2,但是有隐藏的return undefined,相当于resolve(undefined),而console.log('async1 end')这行会被加入到微任务里面,因为相当于被放到then里面了。
注意:await async2()的代码会和console.log('async1 start')一样当做普通代码执行,而await async2()是个函数,要找到那个函数然后跳到这个函数里面执行里面的代码。
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')
如果是浏览器报错,那出错的源码那行之后的代码都不会执行了。所以很危险。
解决办法之一是主动抛出异常,使用throw关键字。但是throw后面的代码也不会执行,好处是可以抛出你自定义的问题描述。
// 1.遇到一个错误, 造成后续的代码全部不能执行 // function foo() { // "abc".filter() // console.log("第15行代码") // console.log("-------") // } // foo() // console.log("+++++++++") // const btn = document.querySelector("button") // btn.onclick = function() { // console.log("监听btn的点击") // } // 2.自己封装一些工具 function sum(num1, num2) { if (typeof num1 !== "number") { throw "type error: num1传入的类型有问题, 必须是number类型" } if (typeof num2 !== "number") { throw "type error: num2传入的类型有问题, 必须是number类型" } return num1 + num2 } // 李四调用 const result = sum(123, 321)
抛出异常的信息是一句话的话没什么效果,一般会使用对象类型来抛出去,能显示的内容跟多。当然也可以自定义class来写错误信息。
系统也有已经写好的Error的类可以直接使用。
class HYError { constructor(message, code) { this.errMessage = message this.errCode = code } } // throw抛出一个异常 // 1.函数中的代码遇到throw之后, 后续的代码都不会执行 // 2.throw抛出一个具体的错误信息 function foo() { console.log("foo function1") // 1.number/string/boolean // throw "反正就是一个错误" // 2.抛出一个对象 // throw { errMessage: "我是错误信息", errCode: -1001 } // throw new HYError("错误信息", -1001) // 3.Error类: 错误函数的调用栈以及位置信息 throw new Error("我是错误信息") console.log("foo function2") console.log("foo function3") console.log("foo function4") } function bar() { foo() } bar()
为什么一旦出现报错,后续代码都不执行了呢?
是因为从报错的地方层层向上报错,最终会在浏览器中抛出错误,所以后续代码就中断执行了,如果我们又写捕获处理错误的代码,那后续代码就可以继续执行。
finally是一定会执行的
function foo() { console.log("foo function1") // throw new Error("我是错误信息") console.log("foo function2") console.log("foo function3") console.log("foo function4") } function test() { // 自己捕获了异常的话, 那么异常就不会传递给浏览器, 那么后续的代码可以正常执行 try { foo() console.log("try后续的代码") } catch(error) { console.log("catch中的代码") // console.log(error) } finally { console.log("finally代码") } } function bar() { test() } bar() console.log("--------")
写了try catch捕获异常后的结果,后续代码可以正常执行。