<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          驚艷!可視化的 js:動(dòng)態(tài)圖演示 Promises & Async/Await 的過程!

          共 7356字,需瀏覽 15分鐘

           ·

          2021-03-11 20:54


          • 原文地址:https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke
          • 原文作者:Lydia Hallie

          原由

          你是否運(yùn)行過不按你預(yù)期運(yùn)行的 js 代碼 ?

          比如:某個(gè)函數(shù)被隨機(jī)的、不可預(yù)測時(shí)間的執(zhí)行了,或者被延遲執(zhí)行了。

          這時(shí),你需要從 ES6 中引入的一個(gè)非??岬男绿匦? Promise 來處理你的問題。

          為了深入理解 Promise ,我在某個(gè)不眠之夜,做了一些動(dòng)畫來演示 Promise 的運(yùn)行,我多年來的好奇心終于得到實(shí)現(xiàn)。

          對(duì)于 Promise ,您為什么要使用它,它在底層是如何工作的,以及我們?nèi)绾我宰瞵F(xiàn)代的方式編寫它呢?

          介紹

          在書寫 JavaScript 的時(shí)候,我們經(jīng)常不得不去處理一些依賴于其它任務(wù)的任務(wù)!

          比如:我們想要得到一個(gè)圖片,對(duì)其進(jìn)行壓縮,應(yīng)用一個(gè)濾鏡,然后保存它 。

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

          一旦圖片被成功加載,把這個(gè)圖片值傳到一個(gè) ocmpressImage 函數(shù)中。

          當(dāng)圖片已經(jīng)被成功地重新調(diào)整大小后,在 applyFilter 函數(shù)中為圖片應(yīng)用一個(gè)濾鏡。

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

          最后,代碼很簡單如圖:

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

          使用了大量嵌套的回調(diào)函數(shù),這使我們的代碼閱讀起來特別困難。

          因?yàn)閷懥嗽S多嵌套的回調(diào)函數(shù),這些回調(diào)函數(shù)又依賴于前一個(gè)回調(diào)函數(shù),這通常被稱為 回調(diào)地獄。

          幸運(yùn)的,ES6 中的 Promise 的能很好的處理這種情況!

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

          Promise語法

          ES6引入了Promise。在許多教程中,你可能會(huì)讀到這樣的內(nèi)容:

          Promise 是一個(gè)值的占位符,這個(gè)值在未來的某個(gè)時(shí)間要么 resolve 要么 reject 。

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

          事實(shí)上,它只是讓我感覺 Promise 是一個(gè)奇怪的、模糊的、不可預(yù)測的一段魔法。

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

          我們可以使用一個(gè)接收一個(gè)回調(diào)函數(shù)的 Promise 構(gòu)造器創(chuàng)建一個(gè) promise。

          好酷,讓我們嘗試一下!

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

          Promise 是一個(gè)對(duì)象,它包含一個(gè)狀態(tài) PromiseStatus 和一個(gè)值 PromiseValue。

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

          不過 - 你將永遠(yuǎn)不會(huì)與這個(gè)對(duì)象進(jìn)行交互,你甚至不能訪問 PromiseStatus 和  PromiseValue 這兩個(gè)屬性!

          然而,在使用 Promise 的時(shí)候,這倆個(gè)屬性的值是非常重要的。


          PromiseStatus 的值,也就是 Promise 的狀態(tài),可以是以下三個(gè)值之一:

          • ? fulfilled: promise 已經(jīng)被 resolved。一切都很好,在 promise 內(nèi)部沒有錯(cuò)誤發(fā)生。

          • ? rejected: promise 已經(jīng)被 rejected。哎呦,某些事情出錯(cuò)了。

          • ? pending: promise 暫時(shí)還沒有被解決也沒有被拒絕,仍然處于 pending 狀態(tài)

          好吧,這一切聽起來很棒,但是什么時(shí)候 promise 的狀態(tài)是 pending、fulfilledrejected 呢?  為什么這個(gè)狀態(tài)很重要呢?

          在上面的例子中,我們只是為 Promise構(gòu)造器傳遞了一個(gè)簡單的回調(diào)函數(shù) () => {} 。

          然而,這個(gè)回調(diào)函數(shù)實(shí)際上接受兩個(gè)參數(shù)。

          • 第一個(gè)參數(shù)的值經(jīng)常被叫做 resolveres,它是一個(gè)函數(shù),在 Promise 應(yīng)該解決 resolve 的時(shí)候會(huì)被調(diào)用。

          • 第二個(gè)參數(shù)的值經(jīng)常被叫做 rejectrej,它也是一個(gè)函數(shù),在 Promise 出現(xiàn)一些錯(cuò)誤應(yīng)該被拒絕 reject 的時(shí)候被調(diào)用。

          讓我們嘗試看看當(dāng)我們調(diào)用 resolvereject 方法時(shí)得到的日志。

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

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

          • 當(dāng)我們調(diào)用 resolve 方法時(shí),promise 的狀態(tài)是 fulfilled。

          • 當(dāng)我們調(diào)用 reject 方法時(shí),promise 的狀態(tài)是 rejected

          有趣的是,我讓(Jake Archibald)校對(duì)了這篇文章,他實(shí)際上指出 Chrome 中存在一個(gè)錯(cuò)誤,該錯(cuò)誤當(dāng)前將狀態(tài)顯示為 “ fulfilled” 而不是 “ resolved”。感謝 Mathias Bynens,它現(xiàn)已在Canary 中修復(fù)!??????

          好了,現(xiàn)在我們知道如何更好控制那個(gè)模糊的 Promise 對(duì)象。但是他被用來做什么呢?

          在前面的介紹章節(jié),我展示了一個(gè)獲得圖片、壓縮圖片、為圖片應(yīng)用過濾器并保存它的例子!最終,這變成了一個(gè)混亂的嵌套回調(diào)。

          幸運(yùn)的,Promise 可以幫助我們解決這個(gè)問題!

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

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

          否則,如果在加載文件時(shí)某個(gè)地方有一個(gè)錯(cuò)誤,我們將會(huì)用發(fā)生的錯(cuò)誤拒絕 (reject)promise 。

          讓我們看下當(dāng)我們在終端運(yùn)行這段代碼時(shí)會(huì)發(fā)生什么?

          非???!就像我們所期望的一樣,promise 得到了解析數(shù)據(jù)后的值。

          但是現(xiàn)在呢?我們不關(guān)心整個(gè) promise 對(duì)象,我們只關(guān)心數(shù)據(jù)的值!幸運(yùn)的,有內(nèi)置的方法來得到 promise 的值。

          對(duì)于一個(gè) promise,我們可以使用它上面的 3 個(gè)方法:

          • .then(): 在一個(gè) promise 被 resolved 后調(diào)用
          • .catch(): 在一個(gè) promise 被 rejected 后被調(diào)用
          • .finally(): 不論 promise 是被 resolved 還是 reject 總是調(diào)用

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

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

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

          現(xiàn)在我們可以用這個(gè)值做任何我們想做的事。


          順便提醒一下,當(dāng)你知道一個(gè) promise 總是 resolve 或者總是 reject 的時(shí)候,你可以寫 Promise.resolvePromise.reject,傳入你想要 rejectresolvepromise 的值。

          在下邊的例子中你將會(huì)經(jīng)??吹竭@個(gè)語法。

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

          .then 它自己的執(zhí)行結(jié)果是一個(gè) promise。這意味著我們可以鏈接任意數(shù)量的 .then:前一個(gè) then 回調(diào)的結(jié)果將會(huì)作為參數(shù)傳遞給下一個(gè) then 回調(diào)!

          在 getImage 示例中,為了傳遞被處理的圖片到下一個(gè)函數(shù),我們可以鏈接多個(gè) then 回調(diào)。

          相比于之前最終得到許多嵌套回調(diào),現(xiàn)在我們得到了整潔的 then 鏈。

          完美!這個(gè)語法看起來已經(jīng)比之前的嵌套回調(diào)好多了。

          宏任務(wù)和微任務(wù)(macrotask and microtask)

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

          讓我們?yōu)槟_本添加一些更多的代碼并且再次運(yùn)行它:

          等下,發(fā)生了什么?!

          首先,Start! 被輸出。

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

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

          這里發(fā)生了什么?

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

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

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

          是的!然而,在事件循環(huán)內(nèi)部,實(shí)際上有 2 種類型的隊(duì)列:宏任務(wù)(macro)隊(duì)列 (或者只是叫做 任務(wù)隊(duì)列 )和 微任務(wù)隊(duì)列。

          (宏)任務(wù)隊(duì)列用于 宏任務(wù),微任務(wù)隊(duì)列用于 微任務(wù)。

          那么什么是宏任務(wù),什么是微任務(wù)呢?

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





          (Macro)task:setTimeoutsetIntervalsetImmediate
          Microtask:process.nextTickPromise callbackqueueMicrotask

          我們看到 Promise 在微任務(wù)列表中!當(dāng)一個(gè) Promise 解決 (resolve) 并且調(diào)用它的 then()、catch()finally() 方法的時(shí)候,這些方法里的回調(diào)函數(shù)被添加到微任務(wù)隊(duì)列!

          這意味著 then(),chatch() 或 finally() 方法內(nèi)的回調(diào)函數(shù)不是立即被執(zhí)行,本質(zhì)上是為我們的 JavaScript 代碼添加了一些異步行為!

          那么什么時(shí)候執(zhí)行 then(),catch(),或 finally() 內(nèi)的回調(diào)呢?

          事件循環(huán)給與任務(wù)不同的優(yōu)先級(jí):

          1. 當(dāng)前在調(diào)用棧 (call stack) 內(nèi)的所有函數(shù)會(huì)被執(zhí)行。當(dāng)它們返回值的時(shí)候,會(huì)被從棧內(nèi)彈出。

          2. 當(dāng)調(diào)用棧是空的時(shí),所有排隊(duì)的微任務(wù)會(huì)一個(gè)接一個(gè)從微任務(wù)任務(wù)隊(duì)列中彈出進(jìn)入調(diào)用棧中,然后在調(diào)用棧中被執(zhí)行!(微任務(wù)自己也能返回一個(gè)新的微任務(wù),有效地創(chuàng)建無限的微任務(wù)循環(huán) )

          3. 如果調(diào)用棧和微任務(wù)隊(duì)列都是空的,事件循環(huán)會(huì)檢查宏任務(wù)隊(duì)列里是否還有任務(wù)。如果宏任務(wù)中還有任務(wù),會(huì)從宏任務(wù)隊(duì)列中彈出進(jìn)入調(diào)用棧,被執(zhí)行后會(huì)從調(diào)用棧中彈出!

          讓我們快速地看一個(gè)簡單的例子:

          • Task1: 立即被添加到調(diào)用棧中的函數(shù),比如在我們的代碼中立即調(diào)用它。

          • Task2,Task3,Task4: 微任務(wù),比如 promisethen 方法里的回調(diào),或者用 queueMicrotask 添加的一個(gè)任務(wù)。

          • Task5,Task6: 宏任務(wù),比如 setTimeout 或者 setImmediate 里的回調(diào)

          首先,Task1 返回一個(gè)值并且從調(diào)用棧中彈出。然后,JavaScript 引擎檢查微任務(wù)隊(duì)列中排隊(duì)的任務(wù)。一旦微任務(wù)中所有的任務(wù)被放入調(diào)用棧并且最終被彈出,JavaScript 引擎會(huì)檢查宏任務(wù)隊(duì)列中的任務(wù),將他們彈入調(diào)用棧中并且在它們返回值的時(shí)候把它們彈出調(diào)用棧。

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

          在這段代碼中,我們有宏任務(wù) setTimeout 和 微任務(wù) promise 的 then 回調(diào)。

          一旦 JavaScript 引擎到達(dá) setTimeout 函數(shù)所在的那行就會(huì)涉及到事件循環(huán)。

          讓我們一步一步地運(yùn)行這段代碼,看看會(huì)得到什么樣的日志!

          快速提一下:在下邊的例子中,我正在展示的像 console.log,setTimeoutPromise.resolve 等方法正在被添加到調(diào)用棧中。它們是內(nèi)部的方法實(shí)際上沒有出現(xiàn)在堆棧痕跡中,因此如果你正在使用調(diào)試器,不用擔(dān)心,你不會(huì)在任何地方見到它們。它只是在沒有添加一堆樣本文件代碼的情況下使這個(gè)概念解釋起來更加簡單。

          在第一行,JavaScript 引擎遇到了 console.log() 方法,它被添加到調(diào)用棧,之后它在控制臺(tái)輸出值 Start!。console.log 函數(shù)從調(diào)用棧內(nèi)彈出,之后 JavaScript 引擎繼續(xù)執(zhí)行代碼。

          JavaScript 引擎遇到了 setTimeout 方法,他被彈入調(diào)用棧中。setTimeout 是瀏覽器的原生方法:它的回調(diào)函數(shù) (() => console.log('In timeout')) 將會(huì)被添加到 Web API,直到計(jì)時(shí)器完成計(jì)時(shí)。盡管我們?yōu)橛?jì)時(shí)器提供的值是 0,在它被添加到宏任務(wù)隊(duì)列 (setTimeout 是一個(gè)宏任務(wù)) 之后回調(diào)還是會(huì)被首先推入 Web API。

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

          JavaScript 引擎看到調(diào)用?,F(xiàn)在是空的。由于調(diào)用棧是空的,它將會(huì)去檢查在微任務(wù)隊(duì)列中是否有在排隊(duì)的任務(wù)!是的,有任務(wù)在排隊(duì),promisethen 中的回調(diào)函數(shù)正在等待輪到它!它被彈入調(diào)用棧,之后它輸出了 promise 被解決后( resolved )的值: 在這個(gè)例子中的字符串 Promise!。

          JavaScript 引擎看到調(diào)用棧是空的,因此,如果任務(wù)在排隊(duì)的話,它將會(huì)再次去檢查微任務(wù)隊(duì)列。此時(shí),微任務(wù)隊(duì)列完全是空的。

          到了去檢查宏任務(wù)隊(duì)列的時(shí)候了:setTimeout 回調(diào)仍然在那里等待!setTimeout 被彈入調(diào)用棧?;卣{(diào)函數(shù)返回 console.log 方法,輸出了字符串 In timeout!。setTimeout 回調(diào)從調(diào)用棧中彈出。

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

          Async/Await

          ES7 引入了一個(gè)新的在 JavaScript 中添加異步行為的方式并且使 promise 用起來更加簡單!隨著 asyncawait 關(guān)鍵字的引入,我們能夠創(chuàng)建一個(gè)隱式的返回一個(gè) promiseasync 函數(shù)。但是,我們該怎么做呢?

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

          我們現(xiàn)在能夠創(chuàng)建隱式地返回一個(gè)對(duì)象的異步函數(shù),而不是顯式地使用 Promise 對(duì)象!這意味著我們不再需要寫任何 Promise 對(duì)象了。

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

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

          當(dāng)我們運(yùn)行下面的代碼塊時(shí)讓我們看下發(fā)生了什么:

          額,這里發(fā)生了什么呢?

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

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

          函數(shù)體繼續(xù)執(zhí)行,將我們帶到第二行。最終,我們看到一個(gè)await關(guān)鍵字!

          最先發(fā)生的事是被等待的值執(zhí)行:在這個(gè)例子中是函數(shù)one。它被彈入調(diào)用棧,并且最終返回一個(gè)解決狀態(tài)的promise。一旦Promise被解決并且one返回一個(gè)值,JavaScript遇到了await關(guān)鍵字。

          當(dāng)遇到await關(guān)鍵字的時(shí)候,異步函數(shù)被暫停。函數(shù)體的執(zhí)行被暫停,async函數(shù)中剩余的代碼會(huì)在微任務(wù)中運(yùn)行而不是一個(gè)常規(guī)任務(wù)!

          現(xiàn)在,因?yàn)橛龅搅?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">await關(guān)鍵字,異步函數(shù)myFunc被暫停,JavaScript引擎跳出異步函數(shù),并且在異步函數(shù)被調(diào)用的執(zhí)行上下文中繼續(xù)執(zhí)行代碼:在這個(gè)例子中是全局執(zhí)行上下文!?♀?

          最終,沒有更多的任務(wù)在全局執(zhí)行上下文中運(yùn)行!事件循環(huán)檢查看看是否有任何的微任務(wù)在排隊(duì):是的,有!在解決了one的值以后,異步函數(shù)myFunc開始排隊(duì)。myFunc被彈入調(diào)用棧中,在它之前中斷的地方繼續(xù)運(yùn)行。

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

          最終,所有的事情都完成了!你注意到async函數(shù)相比于promisethen有什么不同嗎?await關(guān)鍵字暫停了async函數(shù),然而如果我們使用then的話,Promise的主體將會(huì)繼續(xù)被執(zhí)行!

          嗯,這是相當(dāng)多的信息!當(dāng)使用Promise的時(shí)候,如果你仍然感覺有一點(diǎn)不知所措,完全不用擔(dān)心。我個(gè)人認(rèn)為,當(dāng)使用異步JavaScript的時(shí)候,只是需要經(jīng)驗(yàn)去注意模式之后便會(huì)感到自信。

          當(dāng)使用異步JavaScript的時(shí)候,我希望你可能遇到的“無法預(yù)料的”或“不可預(yù)測的”行為現(xiàn)在變得更有意義!

          最后

          如果你喜歡探討技術(shù),或者對(duì)本文有任何的意見或建議,非常歡迎加魚頭微信好友一起探討,當(dāng)然,魚頭也非常希望能跟你一起聊生活,聊愛好,談天說地。

          魚頭的微信號(hào)是:krisChans95 也可以掃碼關(guān)注公眾號(hào),訂閱更多精彩內(nèi)容。


          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩精品免费看 | 翔田千里最猛的A片 | 乱伦大香蕉~ | 欧美www. | 五月丁香激情开心网 |