摘要:引言錯誤理解精心組織起來的異步代碼還不如使用一團亂麻的回調函數(shù)。但是決議后,可以一直保留著這個結果,通過形式添加的回調函數(shù),甚至在異步操作完成之后才添加的回調函數(shù),都會被執(zhí)行調用。
引言
錯誤理解精心組織起來的異步代碼還不如使用一團亂麻的回調函數(shù)。
在處理異步的問題上,回調基本上能夠勝任,不過這都是建立在一切正常運轉的基礎上。
然而事與愿違,回調受到控制反轉的影響,把控制權交給了第三方,這種控制轉移導致了一系列的信任問題(回調調用過早、回調調用過晚、回調不被調用、回調調用次數(shù)過少或過多等問題)。同時,基于回調的異步表達又是無序性的,回調地獄的使用,讓我們正確理解代碼的難度加大。
函數(shù)的確可以規(guī)避以上的問題,但是,毋庸置疑,這會再次加大代碼的理解難度。
與其交給不信任的第三方,倒不如轉交給一個位于我們和第三方間的可信任的中介機制,這里就是我們要說的 Promise。
如何把回調交給 Promise, 其實很簡單。
使用 Promise 后我們就無需再關心大部分的信任問題和無序性。因為 Promise 機制已經為我們處理好了,我們不需要寫些特定邏輯來解決一些信任問題和并發(fā)帶來的競態(tài)問題,只要我們按照 Promise 規(guī)范正確執(zhí)行即可。現(xiàn)在,以 setTimeout 代表異步操作來進行 Promise 改造。
// callback async const callback_async = (x = Date.now(), callback) => { // do something now console.log("callback_async:初始時間戳", x) setTimeout(() => { // do something in the future let interval = Date.now() - x callback && callback(`callback_async:在${interval}毫秒后異步完成`) }, 1000) } callback_async(undefined, res => { console.log("callback_async:", res) })
在 Promise 中我們依然能夠看到回調的身影,只是回調作為參數(shù)傳遞的位置發(fā)生了變化。我們不再把回調交給第三方,而是讓 Promise 從第三方獲取某些數(shù)據(jù),然后回調作為參數(shù)傳遞進去。
const promise_async = (x = Date.now()) => { return new Promise(resolve => { // do something now console.log("promise_async:初始時間戳", x) setTimeout(() => { // do something in the future let interval = Date.now() - x resolve(`promise_async:在${interval}毫秒后異步完成`) }, 1000) }) } promise_async(undefined).then(res => { console.log(res) })
不同之前的把回調直接傳給第三方的做法,這次是靠著 Promise 這個中間機制來替異步任務管理著回調。
錯誤的處理使用 Promise 后,怎么就會好了很多呢?首先說說在錯誤的處理上。
JavaScript 代碼在執(zhí)行的過程中若遇到錯誤就不會執(zhí)行下去的。作為傳入第三方的回調(同步回調或異步回調),如果在此之前就已經報錯了,回調壓根不會執(zhí)行。在這種情況下,能通過回調捕獲錯誤,也是很有意義的。我們很自然地想到了 try...catch , 不過在異步回調中,回調函數(shù)的執(zhí)行棧與原函數(shù)分離開,導致外部是無法抓住異常。不過沒關系,我們就多捕捉一遍。
在此,我們就用“error-first風格”模擬一下。
// callback async const callback_async = (x = Date.now(), callback) => { try { console.log("callback_async:初始時間戳", x) // do something now // throw "callback-outer: error" setTimeout(() => { try { // do something in the future // throw "callback-inner: error" let interval = Date.now() - x callback && callback(null, `callback_async:在${interval}毫秒后異步完成`) } catch (error) { callback(error) } }, 1000) } catch (error) { callback(error) } } callback_async(undefined, (error, res) => { error?console.log("asyncError:", error):console.log("async:", res) })
依次解開注釋 throw ... ,我們就可以成功地捕獲到錯誤或異常。但同時也發(fā)現(xiàn),對于一個不斷嵌套的異步回調,就回調地獄那樣,我們會為每一個異步回調做 try...catch 的錯誤處理,這會使原有的代碼更加混亂。
“幸運”的是,Promise 已經為我們處理好了這個問題。對于錯誤或異常,我們只需要注冊 rejected 或 catch 的回調即可。不過 Promise 也存在著和上面相同的問題,無法捕獲脫離上下文環(huán)境的錯誤或異常,我們只能收到手動 reject。
const promise_async = (x = Date.now()) => { return new Promise((resolve, reject) => { // do something now // throw "promise-outer: error" console.log("promise_async:初始時間戳", x) setTimeout(() => { try { // do something in the future // throw "promise-inner: error" let interval = Date.now() - x resolve(`promise_async:在${interval}毫秒后異步完成`) } catch (error) { reject(error) } }, 1000) }) } promise_async(undefined).catch(error => { console.log(error) })
對于多個異步任務,Promise 仍然能夠很好的處理錯誤,因為 Promise 使用的 this-then-that 的流程控制,默認處理函數(shù)只是把錯誤重新拋出,這使得錯誤可以繼續(xù)沿著Promise鏈傳播下去,直到顯式的 rejected 或 catch 捕獲錯誤。
Promise化Promise 帶來的好處遠遠不止這些。一旦 Promise 決議, 它就永遠保持這個狀態(tài),這個 Promise 的 .then(...) 注冊的回調就會被自動調用,且只會被調用一次。這也算解決了回調調用過少、過多及不被調用的問題。即使不能解決,但也可以在此基礎上再做處理。你要是問為什么,我只能說人家就是干這個的,作為一個可信任的中間協(xié)商機制。
說到一旦決議就不能改變,這個很重要么,是的,真的很重要。
在基于回調模式的異步處理中,JavaScript 代碼執(zhí)行后會一直走下去,遇到回調就直接執(zhí)行了。但是 Promise 決議后,可以一直保留著這個結果,通過 .then(..) 形式添加的回調函數(shù),甚至在異步操作完成之后才添加的回調函數(shù),都會被執(zhí)行調用。這也是上一個 Promise 里的錯誤只能在 Promise 鏈的下一個回調里捕獲的原因。
知道了 Promise 的好處,也知道了基于回調模式的異步處理方式,我們就可以嘗試把“error-first風格”的回調 Promise 化。
// Promise Wrap var promise_wrap = function(fn){ return function() { let args = Array.from(arguments); return new Promise((resolve, reject) => { fn.apply(null, args.concat((error, value) => { error ? reject(error): resolve(value) })) }) } }
在這里我們可以看到,為了統(tǒng)一處理現(xiàn)在和將來,我們把它們都變成了將來,即所有的操作都成了異步,同步回調也變成了異步回調。
JavaScript 異常錯誤也是如此,在 Promise 創(chuàng)建過程中或查看決議結果過程中出現(xiàn)的異常錯誤,這個異常錯誤被捕捉都會變成異步行為。這樣做減少了由函數(shù)順序不確定性(競態(tài)條件)帶來的諸多問題。
保持扁平化從回調模式跨到 Promise,總會不小心保留著原來的風格,比如嵌套。
Promise 鏈式編程最好保持扁平化,不然不就變成另一個回調地獄了?關鍵是還沒有返回或終止 Promise 鏈。
// parallel Promise var parallel_promise = (x = Date.now()) => { Promise.resolve().then(() => { new Promise(resolve => { setTimeout(() => { let interval = Date.now() - x; resolve(`parallel-inner:在${interval}毫秒后完成`) }, 3000) }).then(res => { console.log(res) }) }).then(res => { let interval = Date.now() - x; console.log(`parallel-outer:在${interval}毫秒后完成; res: ${res}`) }) } parallel_promise(undefined)
從上面的執(zhí)行結果可以看出,parallel-outer 并非在 parallel-inner 后執(zhí)行。這是沒有正確將 Promise 相連接的結果。
實際上,這里就是兩個獨立競爭的 Promise(同時在執(zhí)行異步任務而不是一個接著一個)。同時我們也會注意到外層 then(...) 注冊回調中 res 為 undefined,因為對于沒有任何顯式的決議,這個值就是 undefined。
// serial Promise var serial_promise = (x = Date.now()) => { Promise.resolve().then(() => { return new Promise(resolve => { setTimeout(() => { let interval = Date.now() - x; resolve(`serial-1:在${interval}毫秒后完成`) }, 3000) }).then(res => { console.log(res) return res }) }).then(res => { let interval = Date.now() - x; console.log(`serial-2:在${interval}毫秒后完成; res: ${res}`) }) } serial_promise(undefined)
所以說,
一個好的經驗法則是總是返回或終止Promise鏈,并且一旦得到一個新的Promise,返回它。小結
用 Promise 來表達異步和管理并發(fā)無疑是種進步,它在程序的順序性和可信任性上提供了自己的解決方案。它不是回調的替代品,只是幫著異步任務管理回調的可信任的中間機制。
相對于直接粗暴的回調,Promise 并不會帶來性能上的提升,但是它會讓我們的程序更加健壯,也使得代碼更加簡潔,更加符合我們有序的思維方式。
當然,Promise 也有自己的局限性。在并發(fā) Promise.race(...) 上,我們只要第一個決議即可。當出現(xiàn)第一個決議的 Promise 時,其它的 Promise 就沒有必要進行下去了。然而,我們沒把法終止。
在錯誤處理上,Promise 鏈中錯誤總是由下一個 Promise 捕獲。如果錯誤發(fā)生在最后一個 Promise 呢?還有,對于嵌套的 Promise,內部 Promise 已經進行了錯誤處理,但是外部 Promise 卻捕獲不到,這樣真的好么?
Promise 恢復了可信任性,但我們還想讓異步流程的表達風格更貼近同步的形式,鏈式調用不說不好,只是我們帶著同步操作的慣性。還好,ES6、ES7已經給出了方案。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102352.html
摘要:存放成功回調的函數(shù)存放失敗回調的函數(shù)監(jiān)聽回調函數(shù)然后是需要多加一個狀態(tài)判斷,當中是異步操作時,需要在我們之前定義的回調函數(shù)數(shù)組中添加一個回調函數(shù)。參數(shù)函數(shù)返回的對象,函數(shù)的返回值,最外層的上的和。 本文由作者陳旭鋒(任職網易考拉)授權網易云社區(qū)發(fā)布。 Promise源碼詳解學習知識要善于思考,思考,再思考。 —— 愛因斯坦 1.回調地獄曾幾何時,我們的代碼是這樣的,為了拿到回調的結果,...
摘要:一個就像一個樂高玩具。問題是不是你小時候玩兒的那個有趣,它們不是充滿想象力的打氣筒,也不是一種樂高玩具。這是對的并不是給開發(fā)者使用的,它們是給庫作者使用的。不會超過這兩種情況。第二個是根據(jù)第一個處理函數(shù)如何運行來自動變成狀態(tài)成功或者失敗。 原文地址:http://blog.getify.com/promis... 在 Part4:擴展問題 中,我討論了如何擴展和抽象Promise是多么...
摘要:轉載自是什么呢根據(jù)的定義是一個被用于延時計算的最終結果的占位符這個怎么理解呢比如說,我要去麥當勞買點吃的,下單以后人家會先給你一個訂單號,等人家外賣做好了,會提示你,并用那個訂單小票來換取你真正的食物,在這時候,那個訂單小票就是你這頓飯的 轉載自: http://www.lht.ren/article/3/ Promise是什么呢?根據(jù)ecma-262的定義: Promise是一個被用...
摘要:我們先介紹一下中的的一些調用再結合的應用逐步深入。這就是一些簡單的的調用看起來不多,但是靠這個真得解決了許多必須同步并行的環(huán)境本身是一個對象在開始支持。存在兩個回調函數(shù)根據(jù)個人的需求進行處理。 什么是promise?為什么要在nodejs中使用promise?使用promise到底有什么好處呢?實在太多了,一一說來不如直接上實戰(zhàn)。我們先介紹一下nodejs中的promise的一些調用....
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規(guī)范并可配合使用的寫一個符合規(guī)范并可配合使用的理解的工作原理采用回調函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個需求:在系統(tǒng)初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:首先從這個構造函數(shù)說起,它是全局對象的屬性的值,這也就是為什么瀏覽器環(huán)境下我們能直接調用它的原因,就像這些構造函數(shù)一樣。的產生就是像正常使用構造函數(shù)那樣構建一個,不過傳給構造函數(shù)是內部自動創(chuàng)建的,作用是把記錄到中。 showImg(https://segmentfault.com/img/bVbgYy2?w=1200&h=600); > new Promise((resolve, re...
閱讀 2236·2021-11-24 11:15
閱讀 3080·2021-11-24 10:46
閱讀 1378·2021-11-24 09:39
閱讀 3924·2021-08-18 10:21
閱讀 1478·2019-08-30 15:53
閱讀 1395·2019-08-30 11:19
閱讀 3320·2019-08-29 18:42
閱讀 2321·2019-08-29 16:58