十五. async_ await_ 线程
15.1. 认识异步函数
async关键字用于声明一个异步函数:
- async是asynchronous单词的缩写,异步、非同步;
- sync是synchronous单词的缩写,同步、同时;
async异步函数可以有很多中写法:
async function foo1() { }
const foo2 = async () => { }
const foo3 = async function () { }
class Foo {
async bar() { }
}
15.2. async的执行流程
-
异步函数的内部代码执行过程和普通的函数是一致的(async函数内部没有await),默认情况下也是会被同步执行。
// 输出顺序是:1 3 2 async function foo() { console.log(3); } console.log(1); foo() console.log(2);
15.3. 异步和普通函数的区别
-
异步函数有返回值时,和普通函数会有区别:
- 情况一:异步函数也可以有返回值,但是异步函数的返回值会被包裹到Promise.resolve中;
- 情况二:如果我们的异步函数的返回值是Promise,Promise.resolve的状态会由Promise决定;
- 情况三:如果我们的异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定;
async function foo() { console.log(1); console.log('中间代码'); console.log(100); // 1. 返回一个值(数字/字符串/undefined等) // 2. 返回thenable // return { // then:function(resolve, reject){ // resolve('实现了thenable') // } // } // 3. 返回Promise return new Promise((resolve, reject) => { setTimeout(() => { resolve('返回了一个Promise') }, 1000); }) } foo().then(res => { console.log("promise then function exec: " + res); }) -
如果我们在async中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递;
async function foo() { console.log(1); throw new Error('抛出异常') } foo().catch(err => { console.log('err: ' + err); // err: Error: 抛出异常 }) console.log('后续代码会执行~~~'); -
async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。
15.4. async中使用await关键字
await关键字有什么特点呢?
-
通常使用await是后面会跟上一个表达式,这个表达式会返回一个Promise;
-
那么await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数;
function requestData(url) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(url) }, 1000); }) } async function foo(){ // 通常await后面跟的是一个Promise,等Promise回调resolve(结果)时,会得到一个返回值(即resolve的参数),然后把这个返回值结果给res,然后执行then() // 可以用debugger验证,这里和generator有点不一样,具体如下 const res= await requestData('why') debugger // await后面的代码要等到执行resolve()的时候才会依次执行, // 我们知道resolve()执行时,是要执行then里面的回调,但这里没有写then方法,而是直接执行后面的代码了, // 实际上后面的代码就是在then里面执行的,只是内部实现了,我们看不到而已;整体思路和generator思路一样 console.log(res); } foo() -
如果await后面是一个普通的值,那么会直接返回这个值;
async function foo(){ const res= await 123 console.log(res); // 123 } foo() -
如果await后面是一个thenable的对象,那么会根据对象的then方法调用来决定后续的值;
function requestData(url) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(url) }, 1000); }) } async function foo() { const res = await { then: function (resolve, reject) { resolve(111) } } console.log(res); // 111 } foo() -
如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为函数的Promise的reject值;
function requestData(url) { return new Promise((resolve, reject) => { setTimeout(() => { reject(url) }, 1000); }) } async function foo() { await requestData(222) } foo().catch(err => { console.log(err); // 222 })
15.5. 进程和线程
线程和进程是操作系统中的两个概念:
- 进程(process):计算机已经运行的程序,是操作系统管理程序的一种方式;
- 线程(thread):操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中;
听起来很抽象,这里还是给出我的解释:
- 进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程);
- 线程:每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称之为主线程;
- 所以我们也可以说进程是线程的容器;
操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?
- 这是因为CPU的运算速度非常快,它可以快速的在多个进程之间迅速的切换;
- 当我们进程中的线程获取到时间片时,就可以快速执行我们编写的代码;
- 对于用户来说是感受不到这种快速的切换的;
我们经常会说JavaScript是单线程的,但是JavaScript的线程应该有自己的容器进程:浏览器或者Node。
-
浏览器是一个进程吗,它里面只有一个线程吗?
- 目前多数的浏览器其实都是多进程的,当我们打开一个tab页面时就会开启一个新的进程,这是为了防止一个页 面卡死而造成所有页面无法响应,整个浏览器需要强制退出;
- 每个进程中又有很多的线程,其中包括执行JavaScript代码的线程;
JavaScript的代码执行是在一个单独的线程中执行的:
- 这就意味着JavaScript的代码,在同一个时刻只能做一件事;
- 如果这件事是非常耗时的,就意味着当前的线程就会被阻塞;
所以真正耗时的操作,实际上并不是由JavaScript线程在执行的:
- 浏览器的每个进程是多线程的,那么浏览器的其他线程可以来完成这个耗时的操作;
- 比如网络请求、定时器,我们只需要在特性的时候执行应该有的回调即可;