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