我終於逃離了Node(你也能)

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"本文最初發佈於acco.io網站,經原作者授權由InfoQ中文站翻譯並分享。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我在2013年編寫了自己的第一個Node程序。(它是用CoffeeScript寫的。)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"那個時候,Node的優勢主要體現在三個方面:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"第一個是“無處不在的JavaScript”。這句話一開始的意思是“前端使用JavaScript,後端也使用JavaScript”,我一直覺得這個理由沒那麼強勢。(後來它演變成了“強大就是正義”,將JavaScript視爲一種通用語言,這樣看起來就更有說服力了。)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"第二個優勢來自於Node所沒有的部分。當時業界的潮流是反對過去的大一統理念的,像Ruby on Rails和ASP.NET這樣的單體框架逐漸失寵。這條理由也不是很站得住腳,畢竟Ruby的服務條款也沒強迫人們用Rails(看看Stripe)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"第三個優勢是到目前爲止最重要的。當時硅谷的主流框架(Ruby on Rails)還沒有足夠的併發能力,而Node卻有着相當穩固的併發特性基礎。大家都會用JavaScript,而且回調的併發門檻比當時許多線程模型要低很多。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Node的併發特性讓它如虎添翼,迅速流行於世。但我記得就算在很早的時候,有些東西也感覺不對勁。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"當時我們有一個服務,它會看上去隨機地突然在1-2秒內莫名其妙地丟棄數百個請求。經過我對Node的第一次深入探索,我發現了原因所在:一個未捕獲的異常殺掉了服務器上的單個進程。在那段1-2秒的空檔期裏,什麼東西都沒回來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們把過錯歸結爲自己過早用上了新技術。但我不知道過去的幾年中這種事情是否發生了很大變化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我在Deno v1版本中發現了這段話,背後頗有深意:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個hello-world Deno HTTP服務器每秒大約處理25,000個請求,最大延遲爲1.3毫秒;對應的Node程序每秒處理34,000個請求,"},{"type":"text","marks":[{"type":"strong"}],"text":"最大延遲在2到300毫秒之間"},{"type":"text","text":"。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我想起了自己從數據結構中學到的一課。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"數據結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我曾經和一位好友Sacha一起從事一個軟件項目。那時他非常沉迷數據結構。當時我們在西貢,收拾好筆記本電腦去了一個偏遠的柬埔寨小島,象島。我們和世界的聯繫被切斷了,島上我們住的海灘平房沒有互聯網接入。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"那些日子我們會在白天獨立工作,並在日落時分在附近的小屋中共進晚餐。在第一個晚上,我想談論架構和算法,而Sacha想說的只有數據結構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"第二天晚上前,我已經開始着手打造項目的幾個工作流了。晚飯時見到Sacha,看起來好像他幾乎沒合過眼。他告訴我他熬了一宿,四處踱步、發散思維、畫圖、做實驗。早晨他做了點瑜伽,然後一個白天很快就過去了。到最後,他終於在數據結構上取得了一些突破。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我當然要問了:“Sacha,爲什麼總要關注數據結構呢?”他的回答引用了一句名言,後來我知道是Rob Pike說過的話:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據是關鍵。如果你選擇了正確的數據結構並組織得當,那麼算法往往就能自然體現出來。數據結構纔是編程的核心,算法並不是。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"從那一刻起,我在哪裏都能體會到這個道理。如果我覺得自己的程序變得太複雜或太難讀懂,那問題基本都來自於數據結構。從那時起,每次我被其他程序員的代碼打動的時候,都不是因爲代碼用了聰明的技巧或者算法,而是因爲我從代碼中能看出程序員對程序數據應有結構的獨到眼光。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這一原則將數據結構視爲大廈的基礎。如果你打下了堅實的地基,那麼房子蓋起來就不費吹灰之力。如果地基是垃圾堆上的一灘軟泥,那麼蓋房子的時候就會有大麻煩在前面等着你了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"從廣義上來說,這條原則適用於各種工具。你希望儘可能減少揮動大錘時用的力氣,因此設計錘子時應該讓錘身比手柄重很多,這樣就可以發揮槓桿作用。如果你的設計是反過來的,用起來費的力氣就要大很多了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"雖然併發是Node迅速風靡業界的核心動力,但Node的併發一直存在一些根本缺陷。它是一把大錘,但錘子的設計卻是反過來的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"回調從來都不是最優選項,我對這一論點很有自信,因爲幾乎沒有人在全新的領域中使用它們。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們也可以這麼說Promise,因爲async\/await是專門用來抽象它們的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"但房子的第二層遲早會蓋好的,async\/await也遲早會被抽象掉。它很反直覺,就算你習慣了也是如此。我認爲一個不錯的觀點是紅藍函數的理念。在JavaScript中,紅色函數(異步)可以調用藍色函數(同步),但反過來是不行的。這兩種調用的語法也不同。當引入一個紅色函數時,它會在你的代碼庫中流血,染紅許多二級和三級函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Async\/await和事件循環是一個奇怪的範式。很難向新手程序員解釋清楚其中的機制。而且這種機制簡直就像是程序員的基礎沒打好的時候會引入的那種算法缺陷。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"認知開銷"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"人們在研究抽象概念時,爲了方便理解會把它們映射到近似的物理實體上。經過漫長的進化,我的大腦可以想象出遙遠的叢林中漿果的大致數量和顏色。它的進化路線上並沒有軟件編程的份兒。但事實證明,大腦可以使用原本打算用在野外生存的那套神經來很好地完成編程任務。在我的腦海中,我的程序處於一個3維平面上,“在這裏”的一個文件裏的函數會調用“在那裏”的一個文件中的函數。現實情況會有很大差異,但是我們創建的抽象(從文件系統到編譯器再到顯示內容)打通了這一橋樑。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"談到併發性時,對我的大腦而言,任務就是最優雅的映射。任務可以是Elixir中的一個進程,或者是Go中的一個Goroutine。人腦很容易想象出一個worker執行一個任務的畫面:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我想同時請求這個API的前五頁,然後將結果打包在一起交付給客戶端。因此我讓五個小夥伴來做事,每個小夥伴都發出一個請求,向他們每個人分配一個要抓取的頁面——這樣就可以了。現在我只要坐下來等待每個小夥伴回來報告結果就行。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"但對我來說,回調或Promise的想法總是需要一些額外的CPU資源。就像光子擊中了半鍍銀的鏡子一樣:程序被拆分成兩條世界線。在一條線中,控制流繼續運作;在另一條線中,在未來的某個不確定的時間點,程序會執行一個回調或promise。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Async\/await是一種摺疊範式,讓它更容易理解的嘗試。它讓你的程序在某些層面“感覺”上更同步。但這種抽象並不完美,並且放在了錯誤的堆棧層上。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"查詢數據庫"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在Node中,假設你要在一個REPL裏查詢數據庫,如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"> const { Client } = require(\"pg\");\nundefined\n> client = new Client(\/* connection string *\/);\nundefined\n> client.query(\"select now()\");\nPromise { }\n>"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這種事情總有點讓人感到不爽。從理性上講,我可以接受:沒有損失,沒有收穫。如果我想坐上Node的異步火箭登陸月球,我必須接受這類情況下的反人性機制。我得到了一個promise,而不是一個結果,所以我需要添加其他邏輯來處理這個promise並獲得結果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"生活要是那麼輕鬆就好了:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"> await client.query(\"select now()\");\nREPL11:1\nawait client.query(\"select now()\");\n^^^^^\nSyntaxError: await is only valid in async function"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"哎,沒轍,我們都已經習慣了“這裏應該用新辦法做事”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"目前,由於Async\/await的泛濫,我已經想不起Promise實例的API怎麼用了。所以我只能一路回到回調上。還好回調還能用,因爲JavaScript的“不拋棄任何人”原則會確保到我孫子的那一代,回調還能得到很好的支持:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"> client.query('select now()', (err, res) => console.log(res))\nundefined"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"還是沒有結果。抓耳撓腮五分鐘以後,我終於意識到——感謝馮·諾伊曼——我忘了調用client.connect()。如果你在調用client.query()之前不調用client.connect(),pg客戶端會以靜默的方式將這個查詢推送到一個內部隊列。這裏不太容易理解,而且非常容易讓人煩躁——記住我們的基礎是有缺陷的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"所以到最後我調用了connect(),然後是query(),然後終於得到一個結果(夾在這裏面……):"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"> client.connect()\nundefined\n> client.query('select now()', (err, res) => console.log(res))\nResult {\n command: 'SELECT',\n rowCount: 1,\n oid: null,\n rows: [ { now: 2021-03-20T19:32:42.621Z } ],\n fields: [\n Field {\n name: 'now',\n tableID: 0,\n columnID: 0,\n dataTypeID: 1184,\n dataTypeSize: 8,\n dataTypeModifier: -1,\n format: 'text'\n }\n ],\n _parsers: [ [Function: parseDate] ],\n _types: TypeOverrides {\n _types: {\n getTypeParser: [Function: getTypeParser],\n setTypeParser: [Function: setTypeParser],\n arrayParser: [Object],\n builtins: [Object]\n },\n text: {},\n binary: {}\n },\n RowCtor: null,\n rowAsArray: false\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在我這麼多年只編寫Node程序的日子裏,我永遠不會忘記當我第一次在Elixir的REPL,iex中做一個SQL查詢的那一刻。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我啓動了一個與遠程Postgres數據庫的連接:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"iex()> opts = # connection opts\niex()> {:ok, conn} = Postgrex.start_link(opts)\n{:ok, #PID<0.1330.0>}\niex()> ▊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我輸入查詢內容:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"iex()> Postgrex.query(conn, \"select now()\")▊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我按下了回車鍵:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"iex()> Postgrex.query(conn, \"select now()\")\n▊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"你知道REPL接下來做了什麼嗎?它卡了。零點幾秒的時間,這隻美麗的小蟲子就卡在那裏。我永遠不會忘記,它看起來像這樣,持續了幾毫秒:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"iex()> Postgrex.query(conn, \"select now()\")\n▊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我感覺很難喘上氣,然後我的結果出來了:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"iex()> Postgrex.query(conn, \"select now()\")\n{:ok,\n%Postgrex.Result{\n columns: [\"now\"],\n command: :select,\n connection_id: 88764,\n messages: [],\n num_rows: 1,\n rows: [[~U[2021-03-20 19:40:13.378111Z]]]\n}}\niex()> ▊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Elixir怎麼能避免它呢?像這樣的I\/O操作不就是你用到async的地方嗎?我是否以某種方式在REPL中關閉了異步?難道Elixir不是異步的嗎?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"不,Elixir可以避免這種情況,因爲它是建立在Erlang\/OTP之上的,而Erlang\/OTP具有很好的併發性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"從一開始,併發及支持它的流程就已經成爲OTP的一部分。不是作爲一個特性,而是其存在的一部分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"當我運行上面的Postgrex.start_link時,這個函數會向我返回一個pid,我將其存儲在變量conn中。pid是一個地址。在這裏,Postgrex啓動了一個進程,該進程管理與我的Postgres數據庫的連接。這個進程在後臺某處運行,pid是指向該進程的指針。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"當我運行Postgrex.query(conn, statement)時,我傳遞給query\/2的第一個參數是連接進程的pid。REPL——也就是我正在輸入的進程——將statement作爲一條消息發給連接進程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這裏很容易想象出兩個朋友之間互相發消息的畫面。對我這個程序員來說,很重要的一點是REPL會高興地等待,直到它從連接進程中收到回覆。對query\/2的調用是同步的,之所以是同步的,是因爲它可以是同步的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"實際上,每當一個進程執行任何操作時,它始終是同步的。在本地級別,Elixir\/Erlang程序員一直都在考慮同步、功能簡化。在向其他進程發送和接收消息時也是一樣。(而且完全用不着紅色\/藍色函數二分法。)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在Elixir和Erlang中,併發不是在函數層發生,而是在模塊層發生。你可以將模塊實例化爲一個進程,現在它與其他進程併發運行。每個進程都保持自己的狀態,並且可以與其他進程來回傳遞消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"你最後得到的併發範式是人們可以輕鬆與現實映射並理解的。我感到自己是在正確的堆棧層理解併發的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"實際上,對於Elixir\/Erlang程序員而言,正確地建模進程模塊與正確地建模數據結構是一樣重要的。我認爲這就是爲什麼這麼多的人將這些語言描述爲“樂在併發中”的原因所在。這就是當你在數據結構方面取得突破,幹掉400行的複雜邏輯時獲得的那種喜悅。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"創造vs進化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"思考一下Elixir的歷史:首先是由Joe Armstrong和他的團隊發明的Erlang,旨在解決電信行業帶來的巨大併發挑戰。Erlang經過了多年的實戰測試,然後José Valim和他的團隊創建了Elixir,其唯一目標是通過大量借鑑有史以來最人性化的那些語言的優點,來做出一種更爲人性化的產物。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"當然,你最後會得到一些用起來非常愉快的獨特體驗。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"像Elixir和Ruby之類的語言都是創造的行爲。例如Ruby就只有一位創造者和設計師(Matz)。你從第一次接觸該語言時就可以感受到他的感情。它很友好,用起來很舒服,什麼內容都適得其所,有理有據。Ruby的最小驚訝原則讓一切都井井有條。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"JavaScript恰恰相反:JavaScript是一種進化。Node在每個層面,對所有人而言都充滿驚奇的事情。JavaScript總能在細微之處找到破壞你、羞辱你的方法。它不是一個人的設計作品,只是自然選擇的冷酷結果。它到處都是神祕的進化怪癖。不管是好是壞,它都是完全民主的結果,是人民的語言。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"JavaScript的歷史是複雜而深刻的,也許有一天世界會坍縮爲一個奇點,我等不及看到那一天的來臨了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"但強大並不等於正義,我很高興自己終於擺脫它的束縛了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/acco.io\/i-escaped-node","title":null,"type":null},"content":[{"type":"text","text":"https:\/\/acco.io\/i-escaped-node"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章