從地域到天堂:異步編程模式的革命性演變
作為計(jì)算機(jī)早期的一名軟件工程師,你面臨一個(gè)棘手的問(wèn)題。
這一時(shí)期操作系統(tǒng)中已經(jīng)發(fā)明了線程,線程非常好用,因此快速流行起來(lái)。
但在一些特定場(chǎng)景下線程也有自己的問(wèn)題,尤其是后端服務(wù)器。
大家都會(huì)創(chuàng)建一個(gè)線程來(lái)處理用戶請(qǐng)求,如果在此期間發(fā)起一個(gè)阻塞調(diào)用(比如調(diào)用下游服務(wù)),那么整個(gè)線程都會(huì)被掛起,在此期間,線程不能執(zhí)行任何其他工作:
get_response();  // 線程被阻塞
parse_response();在高并發(fā)場(chǎng)景下,這需要?jiǎng)?chuàng)建大量線程,導(dǎo)致巨大的線程創(chuàng)建/切換開銷和內(nèi)存消耗。
這就是最開始的同步編程模型,它簡(jiǎn)單、直觀,但并不高效。
回調(diào)函數(shù)與異步編程
為了解決同步編程模型低效的問(wèn)題你發(fā)明了一個(gè)簡(jiǎn)單而優(yōu)雅概念:回調(diào)函數(shù),callback。
線程不再原地阻塞等待操作完成,而是提供一個(gè)函數(shù),告訴系統(tǒng)"當(dāng)操作完成時(shí),調(diào)用這個(gè)函數(shù)"。
get_response(parse_response);  // get_response直接返回這里,parse_response就是回調(diào)函數(shù),意思是得到下游的response后用parse_response來(lái)處理結(jié)果,這樣get_response會(huì)立刻返回不會(huì)阻塞線程,parse_response會(huì)在其它線程中被執(zhí)行。
至此,你發(fā)明了異步編程。
利用異步編程你不必創(chuàng)建大量線程就能高并發(fā)處理請(qǐng)求。
但隨著項(xiàng)目復(fù)雜度增加,你開始遇到新的問(wèn)題。
回調(diào)地獄:噩夢(mèng)的開始
當(dāng)你需要執(zhí)行一系列依賴的異步操作時(shí),回調(diào)函數(shù)開始變得難以管理。例如,你需要先獲取用戶信息,然后獲取用戶的訂單,最后獲取訂單的詳細(xì)信息:
getUser(userId, function(user) {
  getUserOrders(user.id, function(orders) {
    getOrderDetails(orders[0].id, function(details) {
      displayOrderDetails(details);
    }, function(error) {
      handleOrderDetailsError(error);
    });
  }, function(error) {
    handleOrdersError(error);
  });
}, function(error) {
  handleUserError(error);
});這種代碼結(jié)構(gòu)被形象地稱為"回調(diào)地獄"(Callback Hell),回調(diào)函數(shù)本身會(huì)切割處理邏輯,而隨著嵌套層級(jí)的增加,代碼變得越來(lái)越難以閱讀和維護(hù)。
使用回調(diào)函數(shù)實(shí)現(xiàn)復(fù)雜的控制流(如并行執(zhí)行多個(gè)異步操作,或者有條件地執(zhí)行異步操作)非常困難,你需要新的異步編程范式。
經(jīng)過(guò)反復(fù)思考,你意識(shí)到問(wèn)題的核心在于:回調(diào)函數(shù)并沒(méi)有優(yōu)雅的以線性方式組合異步操作。
該怎么解決這個(gè)問(wèn)題呢?
未來(lái)還是現(xiàn)在?
很簡(jiǎn)單,再來(lái)一層抽象,這種抽象需要?jiǎng)?chuàng)造一種“時(shí)間容器”,將“值”與“計(jì)算過(guò)程”分離,然后以鏈?zhǔn)秸{(diào)用的方式編排異步操作。
這種抽象就是promise/future。
Promise(或在某些語(yǔ)言中稱為Future)。這是一個(gè)代表"未來(lái)某個(gè)時(shí)刻會(huì)有結(jié)果"的對(duì)象,但是現(xiàn)在你可以基于這個(gè)對(duì)象進(jìn)行各種操作, Promise最強(qiáng)大的特性是它支持鏈?zhǔn)秸{(diào)用,通過(guò).then()方法,你可以指定當(dāng)未來(lái)的結(jié)果到來(lái)時(shí)要執(zhí)行的操作;通過(guò).catch()方法,你可以處理Promise失敗的情況。
getUser(userId)
  .then(user => getUserOrders(user.id))
  .then(orders => getOrderDetails(orders[0].id))
  .then(details => displayOrderDetails(details))
  .catch(error => handleError(error));這種鏈?zhǔn)浇Y(jié)構(gòu)成功的實(shí)現(xiàn)了以線性方式組合異步操作,它沒(méi)有回調(diào)函數(shù)那樣的深度嵌套。
除了鏈?zhǔn)秸{(diào)用,Promise還提供了強(qiáng)大的組合功能,例如Promise.all()可以并行執(zhí)行多個(gè)Promise,當(dāng)所有Promise都成功時(shí)返回所有結(jié)果的數(shù)組。
這些組合方法讓你能夠輕松處理復(fù)雜的并發(fā)場(chǎng)景,這在回調(diào)模式中是極其困難的。
同步加異步
Promise徹底改變了你處理異步代碼的方式。代碼結(jié)構(gòu)變得更加清晰,錯(cuò)誤處理更加集中,控制流更加靈活。你終于擺脫了回調(diào)地獄的噩夢(mèng)。
然而,隨著你使用Promise的時(shí)間增長(zhǎng),你開始注意到一個(gè)新的問(wèn)題:雖然Promise解決了嵌套問(wèn)題,但它仍然基于回調(diào)機(jī)制(.then()和.catch()方法本質(zhì)上是注冊(cè)回調(diào)函數(shù)),對(duì)于特別復(fù)雜的異步邏輯,代碼仍然不夠直觀。
實(shí)際上,promise/future說(shuō)到底只是一種偽同步代碼風(fēng)格而已,開發(fā)者仍需在“回調(diào)式”思維和“偽同步”代碼之間轉(zhuǎn)換。
那么有可能把同步編程的直觀和異步編程的高效結(jié)合起來(lái)嗎?
你意識(shí)到要想用同步編程來(lái)實(shí)現(xiàn)異步編程的高效就不能在發(fā)起阻塞操作時(shí)真的阻塞線程。















 
 
 




 
 
 
 