当拿到一段 JavaScript 代码时,浏览器或者 Node 环境首先要做的就是;传递给JavaScript 引擎,并且要求它去执行。
执行 JavaScript 并非一锤子买卖,宿主环境当遇到一些事件时,会继续把一段代码传递给 JavaScript 引擎去执行,此外,我们可能还会提供 API 给 JavaScript 引擎,比如setTimeout 这样的 API,它会允许 JavaScript 在特定的时机执行。
一个 JavaScript 引擎会常驻于内存中,它等待着我们(宿主)把 JavaScript 代码或者函数传递给它执行。
我们把宿主发起的任务称为宏观任务,把 JavaScript 引擎发起的任务称为微观任务。
这里每次的执行过程,其实都是一个宏观任务。我们可以大概理解:宏观任务的队列就相当于事件循环。
在宏观任务中,JavaScript 的 Promise 还会产生异步代码,JavaScript 必须保证这些异步代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列
Promise 是 JavaScript 语言提供的一种标准化的异步管理方式。
它的总体思想是,需要进行 io、等待或者其它异步操作的函数,不返回真实结果,而返回一个“承诺”,函数的调用方可以在合适的时机,选择等待这个承诺兑现(通过 Promise 的 then 方法的回调)。
Promise 的基本用法示例如下:
function sleep(duration) { return new Promise(function(resolve, reject) { setTimeout(resolve,duration); }) } sleep(1000).then( ()=> console.log("finished"));
函数 sleep的作用是等候传入参数指定的时长。
微任务始终先于宏任务
总结一下如何分析异步执行的顺序:
首先我们分析有多少个宏任务;
在每个宏任务中,分析有多少个微任务;
根据调用次序,确定宏任务中的微任务执行次序;
根据宏任务的触发规则和调用次序,确定宏任务的执行次序;
确定整个顺序。
稍微复杂的例子:
function sleep(duration) { return new Promise(function(resolve, reject) { console.log("b"); setTimeout(resolve,duration); }) } console.log("a"); sleep(5000).then(()=>console.log("c")); //执行顺序 :a、b、c
这是一段非常常用的封装方法,利用 Promise 把 setTimeout 封装成可以用于异步的函数。
第一个宏观任务中,包含了先后同步执行的 console.log(“a”); 和 console.log(“b”);。
setTimeout 后,第二个宏观任务执行调用了 resolve,然后 then 中的代码异步得到执
行,所以调用了 console.log(“c”),最终输出的顺序才是: a b c。
新特性:async/await:
async/await的运行时基础是 Promise。
async 函数必定返回 Promise,我们把所有返回 Promise 的函数都可以认为是异步函数。
在 function 关键字之前加上 async 关键字,这样,就定义了一个 async 函数,我们可以在其中使用 await 来等待一个 Promise。
function sleep(duration) { return new Promise(function(resolve, reject) { setTimeout(resolve,duration); }) } async function foo(){ console.log("a") await sleep(2000) console.log("b") }
async 函数强大之处在于,它是可以嵌套的。
function sleep(duration) { return new Promise(function(resolve, reject) { setTimeout(resolve,duration); }) } async function foo(name){ await sleep(2000) console.log(name) } async function foo2(){ await foo("a"); await foo("b"); }
这里 foo2 用 await 调用了两次异步函数 foo,可以看到,如果我们把 sleep 这样的异步操作放入某一个框架或者库中,使用者几乎不需要了解 Promise 的概念即可进行异步编程了。
generator/iterator 也常常被跟异步一起来讲,我们必须说明 generator/iterator并非异步代码,只是在缺少 async/await 的时候,一些框架(最著名的要数 co)使用这样的特性来模拟 async/await。
所以有了 async/await 之后,generator/iterator 来模拟异步的方法应该被废弃。