《智能前端技术与实践》——第 2 章 前端开发基础 ——2.6 深度学习中的 JavaScript——2.6.2 JavaScript 异步编程(上): https://developer.aliyun.com/article/1228122?groupCode=tech_library
2.6.2 JavaScript 异步编程
在 Web 浏览器或微信小程序中部署机器学习应用时,经常会采用从服务器端加载现成的 JavaScript 模型或转换 TensorFlow 模型这两种方法。JavaScript 语言采用的是单线程模型,所 以对于网络 I/O 请求等一些耗时较长的任务,通常会通过设置回调函数、使用 Promise 对象、 使用 async/await 函数来处理。本节将介绍 JavaScript 中的事件循环机制及异步任务的处理方法。
JavaScript 语言最大的特点就是单线程。这意味着所有任务都必须同步执行,即前一个任 务完成后,下一个任务才可以开始,但是如果前一个任务耗时较长,就会使后面的任务一直处 于等待状态,从而造成主线程的阻塞,进而影响页面的渲染。
为了解决这个问题,我们一般将 JavaScript 中可以处理的任务分为同步任务和异步任务。 同步任务是指在 JavaScript 主线程上排队执行的任务;异步任务是指在任务队列中执行的任务, 异步执行的运行机制如下。
• 所有同步任务在 JavaScript 主线程上执行,形成一个执行栈。
• 所有异步任务在任务队列中执行,异步任务包括鼠标单击事件、网络请求事件等(任 务队列又分为宏任务和微任务:宏任务包括 script、setTimeout、setInterval、I/O、UI Rendering,微任务包括 process.nextTick、Promise、MutationObserver 等)。
• 当执行栈中所有任务执行完毕时,系统会读取任务队列中的任务,并进入执行栈,由 主线程开始执行。
• 主线程不断重复上述过程。
具体运行过程如图 2-29 所示。
当 JavaScript 主线程运行的时候,会 产生堆(heap)和栈(stack)。栈中的代码 会调用外部 API(WebAPI),它们会将所 有异步任务(click 事件、load 事件、done 事件等)加入任务队列(callback queue) 中。当栈中的代码执行完毕时,主线程就 会读取任务队列,并执行队列中异步任务 对应事件的回调函数。主线程会不断从任 务队列中读取事件,直至所有任务处理完 成,如图 2-30 所示。
接着,介绍深度学习中常用的两种 JavaScript 异步任务解决方案。
1.使用 Promise 对象
对于传统异步任务,通常会采用回调函数处理,但是该方法会造成无限回调,从而使得程 序结构混乱,不利于后期代码的维护。ECMAScript 6 提供了一种新的异步任务解决方案,即 Promise 对象,它代表一个异步操作最终完成或者失败,共有如下 3 种状态。
• 进行中(pending):初始状态,既没有成功,也没有失败。
• 已成功(fulfilled):操作成功。
• 已失败(rejected):操作失败。
Promise 对象状态的改变只有两种可能—由 Pending 变为 Fulfilled 和由 Pending 变为 Rejected。当发生上述任何一种状态改变后(此时我们称为 Resolved),用 Promise 对象的 then() 方法排列起来的相关处理程序就会被调用,该对象成功实现了用同步的方法编写异步的代码, 避免了回调函数引发的无限回调问题,如图 2-31 所示。
代码清单 2-25 展示了一段示例代码。
代码清单 2-25
letmyFirstPromise=newPromise(function(resolve, reject){ setTimeout(function(){ resolve("hahaCoder!"); }, 250); }); myFirstPromise.then(function(successMessage){ console.log("Yay! "+successMessage); });
代码清单2-25 使用setTimeout()来模拟异步代码。当异步代码执行成功时,才会调用 resolve(); 当异步代码失败时,会调用 reject()。其中 successMessage 的值是调用 resolve()方法所传入 的值。