1. async await
async关键字用于声明一个异步函数
async function foo() {
console.log("foo function1")
console.log("foo function2")
console.log("foo function3")
}
异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行
异步函数有返回值时,和普通函数会有区别:
◼ 情况一:异步函数也可以有返回值,但是异步函数的返回值相当于被包裹到Promise.resolve中;
◼ 情况二:如果我们的异步函数的返回值是Promise,状态由会由Promise决定;
◼ 情况三:如果我们的异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定
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")
// }
// }
}
如果我们在async中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递
async function foo() {
console.log("---------1")
return new Promise((resolve, reject) => {
reject("err rejected")
})
}
foo().then(res => {
}).catch(err => {
console.log("继续执行其他的逻辑代码")
})
async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的,通常使用await是后面会跟上一个表达式,这个表达式会返回一个Promise,那么await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数
// 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()
◼ 如果await后面是一个普通的值,那么会直接返回这个值;
◼ 如果await后面是一个thenable的对象,那么会根据对象的then方法调用来决定后续的值;
◼ 如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为函数的Promise的reject值
2. 语法糖async await的原理
使用生成器实现异步请求
// 封装请求的方法: url -> promise(result)
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url)
}, 2000)
})
}
function* getData() {
const res1 = yield requestData("why")
const res2 = yield requestData(res1 + "kobe")
const res3 = yield requestData(res2 + "james")
}
function execGenFn(genFn) {
const generator = genFn()
function exec(res) {
const result = generator.next(res)
if (result.done) return
result.value.then(res => {
exec(res)
})
}
exec()
}
execGenFn(getData)
async await 实现
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url)
}, 2000)
})
}
async function getData() {
const res1 = await requestData("why")
const res2 = await requestData(res1 + "kobe")
const res3 = await requestData(res2 + "james")
}
3. 进程 线程
进程(process):计算机已经运行的程序,是操作系统管理程序的一种方式,我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程
线程(thread):操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中,每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称之为主线程
操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?
◼ 这是因为CPU的运算速度非常快,它可以快速的在多个进程之间迅速的切换;
◼ 当我们进程中的线程获取到时间片时,就可以快速执行我们编写的代码;
◼ 对于用户来说是感受不到这种快速的切换的
4. 浏览器中的JavaScript线程
我们经常会说JavaScript是单线程(可以开启workers)的,但是JavaScript的线程应该有自己的容器进程:浏览器或者Node
目前多数的浏览器其实都是多进程的,当我们打开一个tab页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出,每个进程中又有很多的线程,其中包括执行JavaScript代码的线程
JavaScript的代码执行是在一个单独的线程中执行的,这就意味着JavaScript的代码,在同一个时刻只能做一件事,如果这件事是非常耗时的,就意味着当前的线程就会被阻塞
所以真正耗时的操作,实际上并不是由JavaScript线程在执行的,浏览器的每个进程是多线程的,那么其他线程可以来完成这个耗时的操作,比如网络请求、定时器,我们只需要在特性的时候执行应该有的回调即可
5. 宏任务 微任务队列
但是事件循环中并非只维护着一个队列,事实上是有两个队列:
◼ 宏任务队列(macrotask queue):ajax setTimeout setInterval DOM监听 UIRendering等
◼ 微任务队列(microtask queue):Promise的then回调 Mutation Observer API queueMicrotask()等
那么事件循环对于两个队列的优先级是怎么样的呢?
1.main script中的代码优先执行(编写的顶层script代码)
2.在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行,也就是宏任务执行之前,必须保证微任务队列是空的,如果不为空,那么就优先执行微任务队列中的任务(回调)
6. throw try catch
1. throw
throw语句用于抛出一个用户自定义的异常
当遇到throw语句时,当前的函数执行会被停止(throw后面的语句不会执行)
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)
JavaScript已经给我们提供了一个Error类,我们可以直接创建这个类的对象
Error包含三个属性:
-
messsage:创建Error对象时传入的message;
-
name:Error的名称,通常和类的名称一致;
-
stack:整个Error的错误信息,包括函数的调用栈,当我们直接打印Error对象时,打印的就是stack;
function foo() {
console.log("foo function1")
throw new Error("我是错误信息")
}
Error有一些自己的子类:
-
RangeError:下标值越界时使用的错误类型;
-
SyntaxError:解析语法错误时使用的错误类型;
-
TypeError:出现类型错误时,使用的错误类型;
2. try catch
如果我们在调用一个函数时,这个函数抛出了异常,但是我们并没有对这个异常进行处理,那么这个异常会继续传递到上一个函数调用中,而如果到了最顶层(全局)的代码中依然没有对这个异常的处理代码,这个时候就会报错并且终止程序的运行
但是很多情况下当出现异常时,我们并不希望程序直接推出,而是希望可以正确的处理异常,这个时候我们就可以使用try catch
在ES10(ES2019)中,catch后面绑定的error可以省略。
当然,如果有一些必须要执行的代码,我们可以使用finally来执行
7. 面试题
1. 面试题一
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")
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)
async1();
new Promise (function (resolve) {
console.log('promise1')
resolve();
}).then (function () {
console.log('promise2')
})
console.log('script end')