成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從一道Promise執行順序的題目看Promise實現

開發 開發工具
當然我們不只是為了解答一道題,主要還是借此了解Promise的內部機制。讀者如果有時間有興趣可以自行分析,然后再回過頭來比較一下本文的分析。或者你可以跟著下面的思路,操起鼠標和鍵盤和我一起干。

之前在網上看到一道Promise執行順序的題目——打印以下程序的輸出:

  1. new Promise(resolve => { 
  2.     console.log(1); 
  3.     resolve(3); 
  4. }).then(num => { 
  5.     console.log(num) 
  6. }); 
  7. console.log(2) 

這道題的輸出是123,為什么不是132呢?因為我一直理解Promise是沒有異步功能,它只是幫忙解決異步回調的問題,實質上是和回調是一樣的,所以如果按照這個想法,resolve之后應該會立刻then。但實際上并不是。難道用了setTimeout?

如果在promise里面再加一個promise:

  1. new Promise(resolve => { 
  2.     console.log(1); 
  3.     resolve(3); 
  4.     Promise.resolve().then(()=> console.log(4)) 
  5. }).then(num => { 
  6.     console.log(num) 
  7. }); 
  8. console.log(2) 

執行順序是1243,第二個Promise的順序會比***個的早,所以直觀來看也是比較奇怪,這是為什么呢?

Promise的實現有很多庫,有jQuery的deferred,還有很多提供polyfill的,如es6-promise,lie等,它們的實現都基于Promise/A+標準,這也是ES6的Promise采用的。

[[222353]]

為了回答上面題目的執行順序問題,必須得理解Promise是怎么實現的,所以得看那些庫是怎么實現的,特別是我錯誤地認為不存在的Promise的異步是怎么實現的,因為***一行的console.log(2)它并不是***執行的,那么必定有某些類似于setTimeout的異步機制讓上面同步的代碼在異步執行,所以它才能在代碼執行完了之后才執行。

當然我們不只是為了解答一道題,主要還是借此了解Promise的內部機制。讀者如果有時間有興趣可以自行分析,然后再回過頭來比較一下本文的分析。或者你可以跟著下面的思路,操起鼠標和鍵盤和我一起干。

這里使用lie的庫,相對于es6-promise來說代碼更容易看懂,先npm install一下:

  1. npm install lie 

讓代碼在瀏覽器端運行,準備以下html:

  1. <!DOCType html> 
  2. <html> 
  3. <head> 
  4.     <meta charset="utf-8"
  5. </head> 
  6. <body> 
  7.     <script src="node_modules/lie/dist/lie.js"></script> 
  8.     <script src="index.js"></script> 
  9. </body> 
  10. </html> 

其中index.js的內容為:

  1. console.log(Promise); 
  2. new Promise(resolve => { 
  3.     console.log(1); 
  4.     resolve(3); 
  5.     Promise.resolve().then(()=> console.log(4)) 
  6. }).then(num => { 
  7.     console.log(num) 
  8. }); 
  9. console.log(2); 

把Promise打印一下,確認已經把原生的那個覆蓋了,對比如下:

因為原生的Promise我們是打不了斷點的,所以才需要借助一個第三方的庫。

我們在第4行的resolve(3)那里打個斷點進去看一下resolve是怎么執行的,層層進去,***的函數是這個:

我們發現,這個函數好像沒干啥,它就是設置了下self的state狀態為FULFILLED(完成),并且把結果outcome設置為調resolve傳進來的值,這里是3,如果resolve傳來是一個Promise的話就會進入到上圖187行的Promise鏈處理,這里我們不考慮這種情況。這里的self是指向一個Promise對象:

它主要有3個屬性——outcome、queue、state,其中outcome是resolve傳進來的結果,state是Promise的狀態,在第83行的代碼可以查到Promise的狀態總共有3種:

  1. var REJECTED = ['REJECTED']; 
  2. var FULFILLED = ['FULFILLED']; 
  3. var PENDING = ['PENDING']; 

Rejected失敗,fulfilled成功,pending還在處理中,在緊接著89行的Promise的構造函數可以看到,state初始化的狀態為pending:

  1. function Promise(resolver) { 
  2.   if (typeof resolver !== 'function') { 
  3.     throw new TypeError('resolver must be a function'); 
  4.   } 
  5.   this.state = PENDING; 
  6.   this.queue = []; 
  7.   this.outcome = void 0; 
  8.   if (resolver !== INTERNAL) { 
  9.     safelyResolveThenable(this, resolver); 
  10.   } 

并且在右邊的調用棧可以看到,resolver是由Promise的構造函數觸發執行的,即當你new Promise的時候就會執行傳參的函數,如下圖所示:

傳進來的函數支持兩個參數,分別是resolve和reject回調:

傳進來的函數支持兩個參數,分別是resolve和reject回調:

  1. let resolver = function(resolve, reject) { 
  2.     if (success) resolve(); 
  3.     else reject(); 
  4. }; 
  5.   
  6. new Promise(resolver); 

這兩個函數是Promise內部定義,但是要在你的函數里調一下它的函數,告訴它什么時候成功了,什么時候失敗了,這樣它才能繼續下一步的操作。所以這兩個函數參數是傳進來的,它們是Promise的回調函數。Promise是怎么定義和傳遞這兩個函數的呢?還是在剛剛那個斷點的位置,但是我們改變一下右邊調用棧顯示的位置:

上圖執行的thenable函數就是我們傳給它的resolver,然后傳遞onSuccess和onError,分別是我們在resolver里面寫的resolve和reject這兩個參數。如果我們調了它的resolve即onSuccess函數,它就會調236行的handlers.resolve就到了我們***次打斷點的那張圖,這里再放一次:

然后去設置當前Promise對象的state,outcome等屬性。這里沒有進入到193行的while循環里,因為queue是空的。這個地方下文會繼續提到。

接著,我們在then那里打個斷點進去看一下:

then又做了些什么工作呢?如下圖所示:

then可以傳兩個參數,分別為成功回調和失敗回調。我們給它傳了一個成功回調,即上圖劃線的地方。并且由于在resolver里面已經把state置成fulfilled完成態了,所以它會執行unwrap函數,并傳遞成功回調、以及resolve給的結果outcome(還有一個參數promise,主要是用于返回,形成then鏈)。

unwrap函數是這樣實現的:

在167行執行then里傳給Promise的成功回調,并傳遞結果outcome。

這段代碼是包在一個immediate函數里的,這里就是解決Promise異步問題的關鍵了。并且我們在node_modules目錄里面,也發現了lie使用了immediate庫,它可以實現一個nextTick的功能,即在當前代碼邏輯單元同步執行完了之后立刻執行,相當于setTimeout 0,但是它又不是直接用setTimeout 0實現的。

我們重點來看一下它是怎么實現一個nextTick的功能的。immediate里面會調一個scheduleDrain(drain是排水的意思):

  1. function immediate(task) { 
  2.   // 這個判斷先忽略 
  3.   if (queue.push(task) === 1 && !draining) { 
  4.     scheduleDrain(); 
  5.   } 

實現邏輯在這個scheduleDrain,它是這么實現的:

  1. var Mutation = global.MutationObserver || global.WebKitMutationObserver; 
  2. var scheduleDrain = null
  3.   // 瀏覽器環境,IE11以上支持 
  4.   if (Mutation) { 
  5.       // ... 
  6.   }  
  7.   // Node.js環境 
  8.   else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined'
  9.   
  10.   } 
  11.   // 低瀏覽器版本解決方案 
  12.   else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) { 
  13.   
  14.   } 
  15.   // ***實在沒辦法了,用最次的setTimeout 
  16.   else { 
  17.     scheduleDrain = function () { 
  18.       setTimeout(nextTick, 0); 
  19.     }; 
  20.   } 

它會有一個兼容性判斷,優先使用MutationObserver,然后是使用script標簽的方式,這種到IE6都支持,***啥都不行就用setTimeout 0.

我們主要看一下Mutation的方式是怎么實現的,MDN上有介紹這個MutationObserver的用法,可以用它來監聽DOM結點的變化,如增刪、屬性變化等。Immediate是這么實現的:

  1. if (Mutation) { 
  2.     var called = 0; 
  3.     var observer = new Mutation(nextTick); 
  4.     var element = global.document.createTextNode(''); 
  5.     // 監聽節點的data屬性的變化 
  6.     observer.observe(element, { 
  7.       characterData: true 
  8.     }); 
  9.     scheduleDrain = function () { 
  10.       // 讓data屬性發生變化,在0/1之間不斷切換, 
  11.       // 進而觸發observer執行nextTick函數 
  12.       element.data = (called = ++called % 2); 
  13.     }; 
  14.   } 

使用nextTick回調注冊一個observer觀察者,然后創建一個DOM節點element,成為observer的觀察對象,觀察它的data屬性。當需要執行nextTick函數的時候,就調一下scheduleDrain改變data屬性,就會觸發觀察者的回調nextTick。它是異步執行的,在當前代碼單元執行完之后立刻之行,但又是在setTimeout 0之前執行的,也就是說,以下代碼,***行的5是***輸出的:

  1. setTimeout(()=> console.log(5), 0); 
  2. new Promise(resolve => { 
  3.     console.log(1); 
  4.     resolve(3); 
  5.     // Promise.resolve().then(()=> console.log(4)) 
  6. }).then(num => { 
  7.     console.log(num) 
  8. }); 
  9. console.log(2); 

這個時候,我們就可以回答為什么上面代碼的輸出順序是123,而不是132了。***點可以肯定的是1是***輸出的,因為new一個Promise之后,傳給它的resolver同步執行,所以1***打印。執行了resolve(3)之后,就會把當前Promiser對象的state改成完成態,并記錄結果outcome。然后跳出來執行then,把傳給then的成功回調給immediate在nextTick執行,而nextTick是使用Mutation異步執行的,所以3會在2之后輸出。

如果在promise里面再寫一個promsie的話,由于里面的promise的then要比外面的promise的then先執行,也就是說它的nextTick更先注冊,所以4是在3之前輸出。

這樣基本上就解釋了Promise的執行順序的問題。但是我們還沒說它的nextTick是怎么實現的,上面代碼在執行immediate的時候把成功回調push到一個全局的數組queue里面,而nextTick是把這些回調按順序執行,如下代碼所示:

  1. function nextTick() { 
  2.   draining = true
  3.   var i, oldQueue; 
  4.   var len = queue.length; 
  5.   while (len) { 
  6.     oldQueue = queue; 
  7.     // 把queue清空 
  8.     queue = []; 
  9.     i = -1; 
  10.     // 執行當前所有回調 
  11.     while (++i < len) { 
  12.       oldQueue[i](); 
  13.     } 
  14.     len = queue.length; 
  15.   } 
  16.   draining = false

它會先把排水的變量draining設置成true,然后處理完成之后再設置成false,我們再回顧一下剛剛執行immediate的判斷:

  1. function immediate(task) { 
  2.   if (queue.push(task) === 1 && !draining) { 
  3.     scheduleDrain(); 
  4.   } 

由于JS是單線程的,所以我覺得這個draining的變量判斷好像沒有太大的必要。另外一個判斷,當queue為空時,push一個變量進來,這個時候queue只有1個元素,返回值就為1。所以如果之前已經push過了,那么這里就不用再觸發nextTick,因為***次的push會把所有queue回調元素都執行的,只要保證后面的操作有被push到這個queue里面就好了。所以這個判斷是一個優化。

另外,es6-promise的核心代碼是一樣的,只是它把immediate函數改成asap(as soon as possible),它也是優先使用Mutation.

還有一個問題,上面說的resolver的代碼是同步,但是我們經常用Promise是用在異步的情況,resolve是異步調的,不是像上面同步調的,如:

  1. let resolver = function(resolve) { 
  2.     setTimeout(() => { 
  3.         // 異步調用resolve 
  4.         resolve(); 
  5.     }, 2000); 
  6.     // resolver執行完了還沒執行resolve 
  7. }; 
  8. new Promise(resolver).then(num => console.log(num)); 

這個時候,同步執行完resolver,但還沒執行resolve,所以在執行then的時候這個Promise的state還是pending的,就會走到134的代碼(剛剛執行的是132行的unwrap):

它會創建一個QueueItem然后放到當前Promise對象的queue屬性里面(注意這里的queue和上面說的immediate里全局的queue是兩個不同的變量)。然后異步執行結束調用resolve,這個時候queue不為空了:

就會執行queue隊列里面的成功回調。因為then是可以then多次的,所以成功回調可能會有多個。它也是調用immediate,在nextTick的時候執行的。

也就是說如果是同步resolve的,是通過MutationObserver/Setimeout 0之類的方式在當前的代碼單元執行完之后立刻執行成功回調;而如果是異步resolve的,是先把成功回調放到當前Promise對象的一個隊列里面,等到異步結束了執行resolve的時候再用同樣的方式在nextTick調用成功回調。

我們還沒說失敗的回調,但大體是相似的。

【本文是51CTO專欄作者“人人網FED”的原創稿件,轉載請通過51CTO聯系原作者獲取授權】

戳這里,看該作者更多好文

責任編輯:武曉燕 來源: 51CTO專欄
相關推薦

2022-01-21 08:50:15

Promise任務隊列前端

2021-04-28 08:21:21

Promise.any服務器場景

2021-04-27 08:31:37

Promisereject信息

2020-09-24 11:46:03

Promise

2023-09-15 15:31:23

異步編程Promise

2012-05-18 11:17:58

Java多線程

2021-04-29 08:28:24

架構參數傳遞

2017-05-11 20:20:59

JavascriptPromiseWeb

2020-07-29 17:35:08

Promise源碼前端

2021-09-02 12:07:48

Swift 監聽系統Promise

2021-03-27 10:59:45

JavaScript開發代碼

2021-11-10 07:47:49

Python源碼代碼

2021-03-09 07:37:42

技術Promise測試

2021-04-29 21:06:49

有序數組算法

2020-09-18 09:02:20

JavaScript

2020-12-15 08:01:24

Promise參數ES6

2018-04-26 11:23:01

Linuxfork程序

2021-04-13 08:50:21

JS作用域面試題

2024-03-18 13:32:11

2023-12-26 08:10:18

Postgresql數據庫Oracle
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美日韩一二三区 | 九九久久这里只有精品 | 男人的天堂亚洲 | 精品视频一区二区三区在线观看 | 日本不卡一区二区三区在线观看 | 日本精品一区二区三区在线观看 | 91久久国产综合久久 | 亚洲免费在线观看 | 久久精品国产久精国产 | 久久国产一区二区三区 | 欧美激情亚洲 | 中文字幕精品一区二区三区精品 | 国产一区二区精品在线观看 | 伊人久久成人 | 精品国产区| 国产精品色婷婷久久58 | 国产成人午夜高潮毛片 | 日韩一区二区三区在线视频 | 91精品国产乱码久久久久久久久 | 久久久精 | 国产麻豆乱码精品一区二区三区 | 中文字幕欧美一区 | 91在线资源| 久久久久网站 | 久久久www成人免费无遮挡大片 | 精品美女视频在线观看免费软件 | 你懂的国产 | 精品国产乱码久久久久久丨区2区 | 欧美最猛黑人xxxⅹ 粉嫩一区二区三区四区公司1 | 精品一区二区三区四区五区 | www久久av| 亚洲欧美日韩一区 | 天天操综合网站 | 国家一级黄色片 | 中文字幕亚洲欧美日韩在线不卡 | 中文字幕日韩欧美一区二区三区 | 午夜爽爽男女免费观看hd | 国产精品一区二区三区四区 | 视频一区欧美 | 国产精品久久久久久一区二区三区 | 最近日韩中文字幕 |