异步任务运行机制
TypeScript 代码最终会编译成 JavaScript 代码来执行,而 JavaScript 代码是单线程执行的,即同一时间只能执行一个任务。
JavaScript 之所以不支持多线程执行,主要由于它起源于浏览器。作为浏览器脚本,它会与用户进行交互,并操作 UI 的 DOM 结构,如果支持多线程并发操作,将会引起比较复杂的问题,例如,线程1在 DOM 上添加一个节点,而线程2删除该节点,此时操作将出现冲突。为了避免产生这类问题,JavaScript 被设计成了单线程执行模式。
前一个任务结束,后一个任务才开始执行,这是单线程执行的特点。通常,在单线程执行模式下都会以同步编程的形式编写代码,处理器会严格按照既定顺序执行代码,每句代码执行后将立即产生效果或返回执行结果,示例代码如下。
let x=1; console.log(x);
typescript
同步编程的局限在于,如果某句代码或某段代码执行比较耗时(如网络通信、文件存取、数据库连接等),程序将一直等待,直到这句代码或这段代码执行完。在此之前,整个程序都将陷入阻塞。
例如,在浏览器中打开开发工具,在控制台中执行以下代码,会发现浏览器页面无法单击,这是由于同步代码尚未执行完成,程序陷入了阻塞状态,无法执行后续任务。
for (let i = 0; i < 100000000; i++) { let date = new Date(); myDate = date }
typescript
当执行同步任务时,执行顺序如图15-1所示。

如果由于计算量较大而处理不过来,那么其实并无不妥,但大多数情况下 CPU 是闲置的,网络通信、文件读取、数据库连接等往往不占用 CPU,只需长时间等待,因此使用同步编程模式效率很低,体验很差。
于是,JavaScript 设计了异步编程模式,挂起需要等待的网络通信、文件读取、数据库连接等任务,先执行排在后面的无须等待的任务,等那些挂起任务有了返回结果后再执行相关回调任务。
以下代码就是异步编程的一个例子,它会先请求百度首页(第 2 行),等网页结果返回后判断请求是否成功并将 HTML 源码输出到控制台。由于要等待网页返回后才处理,因此将判断是否成功并输出 HTML 源码的代码放到回调函数中,里面的代码(第3~5行)暂时不会执行。接着代码会继续向下运行(第7~8行),先给变量 a 赋值,然后将其输出,等网页返回结果后,才会执行回调函数,输出百度首页的 HTML 源码(第3~5行)。
import * as request from 'request'
request('http://www.baidu.com', function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body) //显示百度首页的HTML源码
}
})
let a = "hello";
console.log(a);
javascript
异步执行的机制如下。
JavaScript 主线程依次执行各个同步任务。当执行到特定代码时,JavaScript 会通知其他非 JavaScript 线程执行任务,这些任务不运行 JavaScript,不进入 JavaScript 主线程。JavaScript 主线程不会等待,而继续执行下一个同步任务。当非 JavaScript 线程执行完成后,会将结果以事件通知的形式存到 JavaScript 任务队列中。当 JavaScript 主线程的所有同步任务执行完成后,会检查 JavaScript 任务队列中有哪些事件,并定位到具体回调函数代码上,然后以同步任务的形式依次执行它们。
JavaScript 是单线程的,是指只有一个线程来执行 JavaScript 脚本。但运行环境( Node.js 或浏览器)不是单线程的,一些 I/O 操作、网络请求、定时器和事件监听等都是由运行环境提供的,是由其他非 JavaScript 线程来完成的,这些非 JavaScript 线程的任务执行完成后,会将结果以事件通知的形式存到任务队列中。只要 JavaScript 主线程中的任务清空了,就会从任务队列中获取任务,然后继续执行,这个过程不断重复,这就是 JavaScript 的异步任务运行机制,如图15-2所示。

基于 JavaScript 的异步运行机制,先后诞生了不同的异步编程模式,主要的异步编程模式如下。
-
回调函数。
-
Promise 对象(ECMAScript 6)。
-
async/await 语法(ECMAScript 7)。
接下来将分别介绍这些异步编程模式。