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

可視化的JavaScript動態圖演示 Promises & Async/Await 的過程

新聞 前端
你是否運行過不按你預期運行的 js 代碼 ?比如:某個函數被隨機的、不可預測時間的執行了,或者被延遲執行了。

原由

你是否運行過不按你預期運行的 js 代碼 ?

比如:某個函數被隨機的、不可預測時間的執行了,或者被延遲執行了。

這時,你需要從 ES6 中引入的一個非常酷的新特性: Promise 來處理你的問題。

為了深入理解 Promise ,我在某個不眠之夜,做了一些動畫來演示 Promise 的運行,我多年來的好奇心終于得到實現。

對于 Promise ,您為什么要使用它,它在底層是如何工作的,以及我們如何以最現代的方式編寫它呢?

介紹

在書寫 JavaScript 的時候,我們經常不得不去處理一些依賴于其它任務的任務!

比如:我們想要得到一個圖片,對其進行壓縮,應用一個濾鏡,然后保存它 。

首先,先用 getImage 函數要得到我們想要編輯的圖片。

一旦圖片被成功加載,把這個圖片值傳到一個 ocmpressImage 函數中。

當圖片已經被成功地重新調整大小后,在 applyFilter 函數中為圖片應用一個濾鏡。

在圖片被壓縮和添加濾鏡后,保存圖片并且打印成功的日志!

最后,代碼很簡單如圖:

可視化的 js:動態圖演示 Promises & Async/Await 的過程

注意到了嗎?盡管以上代碼也能得到我們想要的結果,但是完成的過程并不是友好。

使用了大量嵌套的回調函數,這使我們的代碼閱讀起來特別困難。

因為寫了許多嵌套的回調函數,這些回調函數又依賴于前一個回調函數,這通常被稱為 回調地獄。

幸運的,ES6 中的 Promise 可能很好的處理這種情況!

讓我們看看 promise 是什么,以及它是如何在類似于上述的情況下幫助我們的。

Promise語法

ES6引入了Promise。在許多教程中,你可能會讀到這樣的內容:

Promise 是一個值的占位符,這個值在未來的某個時間要么 resolve 要么 reject 。

對于我來說,這樣的解釋從沒有讓事情變得更清楚。

事實上,它只是讓我感覺 Promise 是一個奇怪的、模糊的、不可預測的一段魔法。

接下來讓我們看看 promise 真正是什么?

我們可以使用一個接收一個回調函數的 Promise 構造器創建一個 promise。

好酷,讓我們嘗試一下!

可視化的 js:動態圖演示 Promises & Async/Await 的過程

等等,剛剛得到的返回只是什么?

Promise 是一個對象,它包含一個狀態 PromiseStatus 和一個值 PromiseValue。

在上面的例子中,你可以看到 PromiseStatus 的值是 pending, PromiseValue 的值是 undefined。

不過 - 你將永遠不會與這個對象進行交互,你甚至不能訪問 PromiseStatus 和 PromiseValue 這兩個屬性!

然而,在使用 Promise 的時候,這兩個屬性的值是非常重要的。


PromiseStatus 的值,也就是 Promise 的狀態,可以是以下三個值之一:

  • ✅ fulfilled: promise 已經被 resolved。一切都很好,在 promise 內部沒有錯誤發生。
  • ❌ rejected: promise 已經被 rejected。哎呦,某些事情出錯了。
  • ⏳ pending: promise 暫時還沒有被解決也沒有被拒絕,仍然處于 pending 狀態

好吧,這一切聽起來很棒,但是什么時候 promise 的狀態是 pending、fulfilled 或 rejected 呢? 為什么這個狀態很重要呢?

在上面的例子中,我們只是為 Promise構造器傳遞了一個簡單的回調函數 () => {} 。

然而,這個回調函數實際上接受兩個參數。

  • 第一個參數的值經常被叫做 resolve 或 res,它是一個函數,在 Promise 應該解決 resolve 的時候會被調用。
  • 第二個參數的值經常被叫做 reject 或rej,它也是一個函數,在 Promise 出現一些錯誤應該被拒絕 reject 的時候被調用。
可視化的 js:動態圖演示 Promises & Async/Await 的過程

讓我們嘗試看看當我們調用 resolve 或 reject 方法時得到的日志。

在我的例子中,把 resolve 方法叫做 res,把 reject 方法叫做 rej。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

太好了!我們終于知道如何擺脫 pending 狀態和 undefined 值了!

  • 當我們調用 resolve 方法時,promise 的狀態是 fulfilled。
  • 當我們調用 reject 方法時,promise 的狀態是 rejected。

有趣的是,我讓(Jake Archibald)校對了這篇文章,他實際上指出 Chrome 中存在一個錯誤,該錯誤當前將狀態顯示為 “ fulfilled” 而不是 “ resolved”。感謝 Mathias Bynens,它現已在Canary 中修復!

好了,現在我們知道如何更好控制那個模糊的 Promise 對象。但是他被用來做什么呢?

在前面的介紹章節,我展示了一個獲得圖片、壓縮圖片、為圖片應用過濾器并保存它的例子!最終,這變成了一個混亂的嵌套回調。

幸運的,Promise 可以幫助我們解決這個問題!

首先,讓我們重寫整個代碼塊,以便每個函數返回一個 Promise 來代替之前的函數。

如果圖片被加載完成并且一切正常,讓我們用加載完的圖片解決 (resolve)promise。

否則,如果在加載文件時某個地方有一個錯誤,我們將會用發生的錯誤拒絕 (reject)promise 。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

讓我們看下當我們在終端運行這段代碼時會發生什么?

可視化的 js:動態圖演示 Promises & Async/Await 的過程

非常酷!就像我們所期望的一樣,promise 得到了解析數據后的值。

但是現在呢?我們不關心整個 promise 對象,我們只關心數據的值!幸運的,有內置的方法來得到 promise 的值。

對于一個 promise,我們可以使用它上面的 3 個方法:

  • .then(): 在一個 promise 被 resolved 后調用
  • .catch(): 在一個 promise 被 rejected 后被調用
  • .finally(): 不論 promise 是被 resolved 還是 reject 總是調用
可視化的 js:動態圖演示 Promises & Async/Await 的過程

.then 方法接收傳遞給 resolve 方法的值。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

.catch 方法接收傳遞給 rejected 方法的值。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

最終,我們擁有了 promise 被解決后 (resolved) 的值,并不需要整個 promise 對象!

現在我們可以用這個值做任何我們想做的事。

順便提醒一下,當你知道一個 promise 總是 resolve 或者總是 reject 的時候,你可以寫 Promise.resolve 或 Promise.reject,傳入你想要 reject 或 resolve 的 promise 的值。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

在下面的例子中你將會經常看到這個語法。

在 getImage 的例子中,為了運行它們,我們最終不得不嵌套多個回調。幸運的,.then 處理器可以幫助我們完成這件事!

.then 它自己的執行結果是一個 promise。這意味著我們可以鏈接任意數量的 .then:前一個 then 回調的結果將會作為參數傳遞給下一個 then 回調!

可視化的 js:動態圖演示 Promises & Async/Await 的過程

在 getImage 示例中,為了傳遞被處理的圖片到下一個函數,我們可以鏈接多個 then 回調。

相比于之前最終得到許多嵌套回調,現在我們得到了整潔的 then 鏈。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

完美!這個語法看起來已經比之前的嵌套回調好多了。

宏任務和為任務(macrotask and microtask)

我們知道了一些如何創建 promise 以及如何提取出 promise 的值的方法。

讓我們為腳本添加一些更多的代碼并且再次運行它:

可視化的 js:動態圖演示 Promises & Async/Await 的過程

等下,發生了什么?!

首先,Start! 被輸出。

好的,我們已經看到了那一個即將到來的消息:console.log('Start!') 在最前一行輸出!

然而,第二個被打印的只是 End!,并不是 promise 被解決的值!只有在 End! 被打印之后,promise 的值才會被打印。

這里發生了什么?

我們最終看到了 promise 真正的力量!盡管 JavaScript 是單線程的,我們可以使用 Promise 添加異步任務!

等等,我們之前沒見過這種情況嗎?

在 JavaScript Event Loop 中,我們不是也可以使用瀏覽器原生的方法如 setTimeout 創建某類異步行為嗎?

是的!然而,在事件循環內部,實際上有 2 種類型的隊列:宏任務(macro)隊列 (或者只是叫做 任務隊列 )和 微任務隊列

(宏)任務隊列用于 宏任務,微任務隊列用于 微任務

那么什么是宏任務,什么是微任務呢?

盡管它們比我在這里介紹的要多一些,但是最常用的已經被展示在下面的表格中!

(Macro)task:setTimeoutsetIntervalsetImmediateMicrotask:process.nextTickPromise callbackqueueMicrotask

我們看到 Promise 在微任務列表中!當一個 Promise 解決 (resolve) 并且調用它的 then()、catch() 或 finally() 方法的時候,這些方法里的回調函數被添加到微任務隊列!

這意味著 then(),chatch() 或 finally() 方法內的回調函數不是立即被執行,本質上是為我們的 JavaScript 代碼添加了一些異步行為!

那么什么時候執行 then(),catch(),或 finally() 內的回調呢?

事件循環給與任務不同的優先級:

  1. 當前在調用棧 (call stack) 內的所有函數會被執行。當它們返回值的時候,會被從棧內彈出。
  2. 當調用棧是空的時,所有排隊的微任務會一個接一個從微任務任務隊列中彈出進入調用棧中,然后在調用棧中被執行!(微任務自己也能返回一個新的微任務,有效地創建無限的微任務循環 )
  3. 如果調用棧和微任務隊列都是空的,事件循環會檢查宏任務隊列里是否還有任務。如果宏任務中還有任務,會從宏任務隊列中彈出進入調用棧,被執行后會從調用棧中彈出!

讓我們快速地看一個簡單的例子:

  • Task1: 立即被添加到調用棧中的函數,比如在我們的代碼中立即調用它。
  • Task2,Task3,Task4: 微任務,比如 promise 中 then 方法里的回調,或者用 queueMicrotask 添加的一個任務。
  • Task5,Task6: 宏任務,比如 setTimeout 或者 setImmediate 里的回調
可視化的 js:動態圖演示 Promises & Async/Await 的過程

首先,Task1 返回一個值并且從調用棧中彈出。然后,JavaScript 引擎檢查為任務隊列中排隊的任務。一旦微任務中所有的任務被放入調用棧并且最終被彈出,JavaScript 引擎會檢查宏任務隊列中的任務,將它們彈入調用棧中并且在它們返回值的時候把它們彈出調用棧。

圖中足夠粉色的盒子是不同的任務,讓我們用一些真實的代碼來使用它!

可視化的 js:動態圖演示 Promises & Async/Await 的過程

在這段代碼中,我們有宏任務 setTimeout 和 微任務 promise 的 then 回調。

一旦 JavaScript 引擎到達 setTimeout 函數所在的那行就會涉及到事件循環。

讓我們一步一步地運行這段代碼,看看會得到什么樣的日志!

快速提一下:在下面的例子中,我正在展示的像 console.log,setTimeout 和 Promise.resolve 等方法正在被添加到調用棧中。它們是內部的方法實際上沒有出現在堆棧痕跡中,因此如果你正在使用調試器,不用擔心,你不會在任何地方見到它們。它只是在沒有添加一堆樣本文件代碼的情況下使這個概念解釋起來更加簡單。

在第一行,JavaScript 引擎遇到了 console.log() 方法,它被添加到調用棧,之后它在控制臺輸出值 Start!。console.log 函數從調用棧內彈出,之后 JavaScript 引擎繼續執行代碼。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

JavaScript 引擎遇到了 setTimeout 方法,他被彈入調用棧中。setTimeout 是瀏覽器的原生方法:它的回調函數 (() => console.log('In timeout')) 將會被添加到 Web API,直到計時器完成計時。盡管我們為計時器提供的只是 0,在它被添加到宏任務隊列 (setTimeout 是一個宏任務) 之后回調還是會被首先推入 Web API。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

JavaScript 引擎遇到了 Promise.resolve 方法。Promise.resolve 被添加到調用棧。在 Promise 解決 (resolve) 值之后,它的 then 中的回調函數被添加到微任務隊列。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

JavaScript 引擎看到調用棧現在是空的。由于調用棧是空的,它將會去檢查在微任務隊列中是否有在排隊的任務!是的,有任務在排隊,promise 的 then 中的回調函數正在等待輪到它!它被彈入調用棧,之后它輸出了 promise 被解決后( resolved )的值: 在這個例子中的字符串 Promise!。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

JavaScript 引擎看到調用棧是空的,因此,如果任務在排隊的話,它將會再次去檢查微任務隊列。此時,微任務隊列完全是空的。

到了去檢查宏任務隊列的時候了:setTimeout 回調仍然在那里等待!setTimeout 被彈入調用棧。回調函數返回 console.log 方法,輸出了字符串 In timeout!。setTimeout 回調從調用棧中彈出。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

終于,所有的事情完成了! 看起來我們之前看到的輸出最終并不是那么出乎意料。

Async/Await

ES7 引入了一個新的在 JavaScript 中添加異步行為的方式并且使 promise 用起來更加簡單!隨著 async 和 await 關鍵字的引入,我們能夠創建一個隱式的返回一個 promise 的 async 函數。但是,我們該怎么做呢?

之前,我們看到不管是通過輸入 new Promise(() => {}),Promise.resolve 或 Promise.reject,我們都可以顯式的使用 Promise 對象創建 promise。

我們現在能夠創建隱式地返回一個對象的異步函數,而不是顯式地使用 Promise 對象!這意味著我們不再需要寫任何 Promise 對象了。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

盡管 async 函數隱式的返回 promise 是一個非常棒的事實,但是在使用 await 關鍵字的時候才能看到 async 函數的真正力量。當我們等待 await 后的值返回一個 resolved 的 promise 時,通過 await 關鍵字,我們可以暫停異步函數。如果我們想要得到這個 resolved 的 promise 的值,就像我們之前用 then 回調那樣,我們可以為被 await 的 promise 的值賦值為變量!

這樣,我們就可以暫停一個異步函數嗎?很好,但這到底是什么意思?

當我們運行下面的代碼塊時讓我們看下發生了什么:

可視化的 js:動態圖演示 Promises & Async/Await 的過程

額,這里發生了什么呢?

可視化的 js:動態圖演示 Promises & Async/Await 的過程

首先,JavaScript 引擎遇到了 console.log。它被彈入到調用棧中,這之后 Before function! 被輸出。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

然后,我們調用了異步函數myFunc(),這之后myFunc函數體運行。函數主體內的最開始一行,我們調用了另一個console.log,這次傳入的是字符串In function!。console.log被添加到調用棧中,輸出值,然后從棧內彈出。

可視化的 js:動態圖演示 Promises & Async/Await 的過程

函數體繼續執行,將我們帶到第二行。最終,我們看到一個await關鍵字!

最先發生的事是被等待的值執行:在這個例子中是函數one。它被彈入調用棧,并且最終返回一個解決狀態的promise。一旦Promise被解決并且one返回一個值,JavaScript遇到了await關鍵字。

當遇到await關鍵字的時候,異步函數被暫停。函數體的執行被暫停,async函數中剩余的代碼會在微任務中運行而不是一個常規任務!

可視化的 js:動態圖演示 Promises & Async/Await 的過程

現在,因為遇到了await關鍵字,異步函數myFunc被暫停,JavaScript引擎跳出異步函數,并且在異步函數被調用的執行上下文中繼續執行代碼:在這個例子中是全局執行上下文!‍♀️

可視化的 js:動態圖演示 Promises & Async/Await 的過程

最終,沒有更多的任務在全局執行上下文中運行!事件循環檢查看看是否有任何的微任務在排隊:是的,有!在解決了one的值以后,異步函數myFunc開始排隊。myFunc被彈入調用棧中,在它之前中斷的地方繼續運行。

變量res最終獲得了它的值,也就是one返回的promise被解決的值!我們用res的值(在這個例子中是字符串One!)調用console.log。One!被打印到控制臺并且console.log從調用棧彈出。

最終,所有的事情都完成了!你注意到async函數相比于promise的then有什么不同嗎?await關鍵字暫停了async函數,然而如果我們使用then的話,Promise的主體將會繼續被執行!

嗯,這是相當多的信息!當使用Promise的時候,如果你仍然感覺有一點不知所措,完全不用擔心。我個人認為,當使用異步JavaScript的時候,只是需要經驗去注意模式之后便會感到自信。

當使用異步JavaScript的時候,我希望你可能遇到的“無法預料的”或“不可預測的”行為現在變得更有意義!

最后

外國友人技術博客的語言表達的方式和風格、與國人的還是有很大差別的啊。

明明看到有很長或者很拗口的句子的時候,我就想按自己的語言來寫一篇了

可能自己寫一篇都比翻譯的快

 

責任編輯:張燕妮 來源: Echa攻城獅
相關推薦

2016-11-22 11:08:34

asyncjavascript

2021-07-20 10:26:12

JavaScriptasyncawait

2024-09-02 14:12:56

2014-07-15 10:31:07

asyncawait

2020-03-23 14:55:52

Python可視化Plotly

2021-09-27 08:31:01

數據可視化柱狀圖折現圖

2023-03-06 16:07:19

梯度提升算法機器學習

2015-11-06 14:04:54

數據可視化信息圖

2020-03-11 14:39:26

數據可視化地圖可視化地理信息

2017-06-19 09:12:08

JavaScriptPromiseAsync

2024-08-20 14:31:16

2023-10-08 10:21:11

JavaScriptAsync

2021-06-07 09:44:10

JavaScript開發代碼

2017-02-15 09:30:01

可視化網絡布局

2021-07-02 14:07:00

可視化Plotly漏斗圖

2023-03-27 23:42:29

樹狀圖開發可視化

2014-04-21 10:14:52

PromisesJavaScript

2021-05-13 15:23:31

人工智能深度學習

2021-06-28 08:10:59

JavaScript異步編程

2017-09-11 13:33:44

大數據數據可視化決策樹
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久国产区 | 成人一区二区三区在线观看 | 狠狠爱一区二区三区 | 亚洲精品久久久久久一区二区 | 国产一区二区三区视频 | 男女羞羞免费视频 | 日韩国产中文字幕 | 人人干人人干人人干 | 狠狠操狠狠 | 色婷婷一区二区三区四区 | 欧美性受xxxx | 在线观看日韩 | 亚洲精品久久久久久久不卡四虎 | 免费成人高清 | 91成人免费看 | 欧美一区二区大片 | 久久久噜噜噜久久中文字幕色伊伊 | 久久久久久国产精品 | 每日更新av | 日本在线看片 | 羞羞的视频在线看 | 亚洲精品视 | 国产精品99一区二区 | 99精品网站| 欧美韩一区二区 | 亚洲一区视频在线 | 午夜黄色影院 | 天天操天天干天天曰 | 久久国产高清视频 | 日韩精品一区二区三区在线播放 | 亚洲精品无人区 | a级在线免费观看 | 色伊人 | 国产福利在线 | 久久性色| 久久久亚洲 | 国产乱码精品一区二区三区中文 | 久婷婷 | 亚洲一区二区三区视频 | 日韩亚洲视频 | 欧美在线视频一区 |