上一章探讨了Generator
的相关知识他也是理解async/await
的一个基础。async/await
是对Promise
处理异步任务同步化的进一步优化,语法简单,是前端人员很喜欢使用的语法糖。那么async/await
背后到底有什么呢:
async/await
优点有什么?asyncFunction
构造函数。async/await
语法,及错误捕获。async/await
运行原理。- 通过
Promise
和Generator
函数,实现一个async/await
。
1. async/await 优点有什么
async/await
关键字让我们可以用一种更简洁的方式写出基于Promise
的异步行为,而无需刻意地链式调用promise
。- 更符合人类的思考方式,使用
Promise
时,需要写.then()
等多个回调,去获取内部的数据,阅读起来多少还是有些不便,async/await
书写顺序即是执行顺序,容易理解。 Promise
是根据函数式编程的范式,对异步过程进行了一层封装,async/await
基于协程的机制,是对异步过程更精确的一种描述。
2. AsyncFunction 构造函数
与Generator
一样,async
函数也是通过构造函数创建出的新异步函数对象。而且也不是一个全局对象。
let AsyncFunction = Object.getPrototypeOf(async function(){}).constructor
async function asyncExpression(){}
let asyncFn = new AsyncFunction()
console.log(asyncExpression instanceof AsyncFunction) // true
console.log(asyncFn instanceof AsyncFunction) // true
// 但不建议这样使用new这个实例,就像创建数组直接使用字面量一样。
// 因为函数表达式更高效,异步函数是与其他代码一起被解释器解析的,而使用new函数体是单独解析的。
调用 AsyncFunction
构造函数时可以省略 new
,其效果是一样的。
3. async/await 语法,及错误捕获
当我们用创建出一个async
函数时,async
一定是返回一个Promise
。
async function foo() {
return 1
}
// 等价与
function foo() {
return Promise.resolve(1)
}
async
意为异步的意思,一定是在此函数中执行异步任务而设立的,当然只写同步任务也没关系,这时async
便是冗余的。- 它与
Generator
函数很像,内部可以有多个await
,执行到await
表达式时会暂停阻塞整个async
函数并等待await
表达式后面的Promise
异步结果,直到拿到Promise
返回的resovle
结果后恢复async
函数的执行。
function promiseFn(){
return new Promise((resovle, reject) => {
setTimeout(() => {
resovle('success')
}, 2000)
})
}
async function foo() {
let a = await promiseFn();
console.log(a);
console.log('after await');
}
console.log(foo()) //两秒后输出 success , after await
await
必须在async
函数体内执行,不然会报语法错误。- 由于
await
是同步执行,如果await
后的异步任务报错,会导致后面代码无法执行,所以要用try/catch
来捕获错误,可以让后续代码继续执行。
function promiseFn(){
return new Promise((resovle, reject) => {
setTimeout(() => {
reject('error')
}, 2000)
})
}
async function foo() {
try{
let a = await promiseFn();
}catch(e) {
console.log(e); //两秒后输出 error
}
}
5.在 async
函数中await
在执行Promise
异步任务时会阻塞async
函数体内后面的代码,但 async
函数调用并不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise
对象中异步执行,这也正是 await
必须用在 async
函数中的原因。
function promiseFn1() {
return new Promise((resovle, reject) => {
setTimeout(() => {
resovle('1success')
}, 2000)
})
};
function promiseFn2() {
return new Promise((resovle, reject) => {
setTimeout(() => {
resovle('2success')
}, 1000)
})
}
async function foo() {
let a = await promiseFn2();
console.log(a);
}
foo()
promiseFn1().then(res => {
console.log(res);
})
// 一秒后输出:2success
// 两秒后输出:1success
6.多个await
且没有互相的依赖时,使用Promise.all
请求可以同时请求,加快速度。
function promiseFn1() {
return new Promise((resovle, reject) => {
setTimeout(() => {
resovle('success1')
}, 10000)
})
}
function promiseFn2() {
return new Promise((resovle, reject) => {
setTimeout(() => {
resovle('success2')
}, 5000)
})
}
async function foo() {
let res = await Promise.all([promiseFn1(), promiseFn2()])
console.log(res); //十秒后 ['success1', 'success2']
}
// 如果是两个await依次进行,得到所有结果的时间为15秒。
foo()
4. async/await运行原理
async/await
是由 generator
+yield
控制流程 + Promise
实现回调。对语法进行了封装。
所以理解 generator
+yield
是如何控制流程的,我会简单介绍进程,线程,协程的概念,并结合 generator
和 async/await
的语法来说明实现过程:
进程
- 简单的理解是一个应用程序的启动实例,打开浏览器,打开vscode,这时就开启了两个进程。
- 广义的定义为: 1)是一个具有独立功能的程序关于某个数据集合的一次运行活动。
2)它是操作系统动态执行的基本单元,进程即是基本的分配单元,也是基本的执行单元。 - 每一个进程都有属于他的地址空间,包含文本区域、数据区域、堆栈。
- 进程本身不会运行,是线程的容器。
- 进程有五种状态:1)创建状态 2)就绪状态 3)执行状态 4)阻塞状态 5)终止状态**
线程
- vscode要编辑代码,打开命令窗口,打开git跟踪,这就是一个线程,多个进程工作。
- 一个进程中至少有一个主线程,也可有更多子线程,不然就没有存在的意义。
- 对操作系统来说,线程是最小的执行单元,线程和进程由操作系统进(CPU)行调度。
- 一个标准的线程由当前的线程ID、当前指令指针、寄存器和堆栈组成。
- 同一个进程中的多个线程之间可以并发执行。(并发: 两个或以上线程同时跳转运行,但同一时间只有一个线程运行)
- 线程状态:1)就绪状态 2)运行状态 3)阻塞状态。
协程
- 是一种比线程更加轻量级的存在,一个线程可以拥有多个协程。
- 协程的调度完全由用户控制。
- 协程拥有自己的寄存器上下文和栈。
- 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快
由于
JavaScript
运行时,是单线程的,利用循环机制来执行代码. 创建一个Generator
函数,就是创建一个协程,可以通过yield
的方式实现协程的切换,完成异步任务,运行流程大致如下:
1)协程A开始执行;
2)协程A执行到一半,进入暂停,执行权转移到协程B;
3)一段时间后,协程B交还执行权;
4)协程A恢复执行;
遇到yield
表达式交出控制权,等得执行结果,拿到结果后,交还执行权并输出。理解了Generator
运行机制就对比着async/await
看看: - 创建
Generator
函数时的*,可以理解为创建Async
函数的async
。
function* gen(){}
async function asy(){}
- 执行到需要暂停下来的地方用
yield
,变为await
,它内部有封装好的next()
,所以不用手动调用。但await
返回的值经过async
包装,始终是Promise
。
function* gen(){
yield 1
}
async function asy(){
await 1
}
所以可以说async/await
是由 generator
+yield
的控制流程 + Promise
的语法封装。
5. 实现一个简单的 async/await
// 定义了一个promise,用来模拟异步请求,作用是传入参数++
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}
//自动执行器,如果一个Generator函数没有执行完,则递归调用
function asyncFun(func){
var gen = func();
function next(data){
var result = gen.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
// 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步
var func = function* (){
var f1 = yield getNum(1);
var f2 = yield getNum(f1);
console.log(f2) ;
};
asyncFun(func);
如果此文章对您有帮助或启发,那便是我的荣幸