在 ES3 和更早的版本中,JavaScript 本身还没有异步执行代码的能力,这也就意味着,宿 主环境传递给 JavaScript 引擎一段代码,引擎就把代码直接顺次执行了,这个任务也就是 宿主发起的任务。

  但是,在 ES5 之后,JavaScript 引入了 Promise,这样,不需要浏览器的安排, JavaScript 引擎本身也可以发起任务了。

由于我们这里主要讲 JavaScript 语言,那么采纳 JSC 引擎的术语,我们把宿主发起的任务称为宏观任务,把 JavaScript 引擎发起的任务称为微观任务。

  在宏观任务中,JavaScript 的 Promise 还会产生异步代码,JavaScript 必须保证这些异步 代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列。

有了宏观任务和微观任务机制,我们就可以实现 JS 引擎级和宿主级的任务了,例如: Promise 永远在队列尾部添加微观任务。setTimeout 等宿主 API,则会添加宏观任务。

Promise

  对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:

  • pending: 初始状态,不是成功或失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

  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,它的作用是等候传入参数指定的时长。Promise 的 then 回调是一个异步的执行过程,下面我们就来研究一下 Promise 函数中的 执行顺序,我们来看一段代码示例: 

var r = new Promise(function(resolve, reject){ 
 console.log("a"); 
 resolve() 
 }); 
 r.then(() => console.log("c")); 
 console.log("b") 

  我们执行这段代码后,注意输出的顺序是 a b c。在进入 console.log(“b”) 之前,毫无疑 r 已经得到了 resolve,但是 Promise 的 resolve 始终是异步操作,所以 c 无法出现在 b 之前。接下来我们试试跟 setTimeout 混用的 Promise。在这段代码中,我设置了两段互不相干的异步操作:通过 setTimeout 执行 console.log(“d”),通过 Promise 执行 console.log(“c”) 

 var r = new Promise(function(resolve, reject){ 
 console.log("a"); 
 resolve() 
 }); 
 setTimeout(()=>console.log("d"), 0) 
 r.then(() => console.log("c")); 
 console.log("b") 

  我们发现,不论代码顺序如何,d 必定发生在 c 之后,因为 Promise 产生的是 JavaScript 引擎内部的微任务,而 setTimeout 是浏览器 API,它产生宏任务。为了理解微任务始终先于宏任务,我们设计一个实验:执行一个耗时 1 秒的 Promise。 

 setTimeout(()=>console.log("d"), 0) 
 var r = new Promise(function(resolve, reject){ 
 resolve() 
 }); 
 r.then(() => { 
 var begin = Date.now(); 
 while(Date.now() - begin < 1000); 
 console.log("c1") 
 new Promise(function(resolve, reject){ 
 resolve() 
 }).then(() => console.log("c2")) 
 }); 

  这里我们强制了 1 秒的执行耗时,这样,我们可以确保任务 c2 是在 d 之后被添加到任务 队列。 我们可以看到,即使耗时一秒的 c1 执行完毕,再 enque 的 c2,仍然先于 d 执行了,这很好地解释了微任务优先的原理。

  通过一系列的实验,我们可以总结一下如何分析异步执行的顺序:

  • 首先我们分析有多少个宏任务;
  • 在每个宏任务中,分析有多少个微任务;
  • 根据调用次序,确定宏任务中的微任务执行次序;
  • 根据宏任务的触发规则和调用次序,确定宏任务的执行次序;
  • 确定整个顺序。

  我们再来看一个稍微复杂的例子:

 

 function sleep(duration) { 
 return new Promise(function(resolve, reject) { 
console.log("b"); 
 setTimeout(resolve,duration); 
}) 
 } 
 console.log("a"); 
 sleep(5000).then(()=>console.log("c")); 

 

  这是一段非常常用的封装方法,利用 Promise 把 setTimeout 封装成可以用于异步的函 数。 我们首先来看,setTimeout 把整个代码分割成了 2 个宏观任务,这里不论是 5 秒还是 0 秒,都是一样的。 第一个宏观任务中,包含了先后同步执行的 console.log(“a”); 和 console.log(“b”);。setTimeout 后,第二个宏观任务执行调用了 resolve,然后 then 中的代码异步得到执 行,所以调用了 console.log(“c”),最终输出的顺序才是: a b c。 Promise 是 JavaScript 中的一个定义,但是实际编写代码时,我们可以发现,它似乎并不 比回调的方式书写更简单,但是从 ES6 开始,我们有了 async/await,这个语法改进跟 Promise 配合,能够有效地改善代码结构。 

 

posted on 2020-09-24 10:52  小名香菜~  阅读(240)  评论(0编辑  收藏  举报