pull和push,是在軟件中消費數據的兩種方式,它們描述了數據生產者(或持有者)與數據消費者之間是如何通訊的。過去我們肯定了解過它們,不過可能會在編程中會忽略它們之間的區別與聯系,本篇文章希望幫助大家理解這兩者的區別於聯系,從而在編程中有意識地分辨與選擇它們。
我們可以用一個現實生活中的例子來理解pull與push:
你某天想要閱讀新聞,於是打開瀏覽器,輸入新聞網站的地址,敲下回車,於是新聞內容展現在你的眼前。這是一個pull模型;
你也可以,下載一個新聞App,設置消息推送功能,讓它時不時向你推送重要的新聞。這是一個push模型。
pull系統
在pull系統中,數據消費者決定自己何時請求並接收數據;數據持有者只能被動地響應請求。
編程語言的函數機制就是pull系統的例子。函數是數據生產者,調用者是數據消費者。調用者在自己需要的時候,調用函數,從函數中“拉”出一個結果,即let result = func(args);。
JavaScript的Generator function則是pull系統的又一個范例,只不過消費者可以多次pull,依次拿出順序有關的多個結果:
function* generator(i) { yield i; yield i + 10; } const gen = generator(10); const result1 = gen.next().value; const result2 = gen.next().value;
Async Iterator與普通Iterator類似,是典型的pull模型,只不過pull的結果是異步返回的:
async function* generateSequence(start, end) { for (let i = start; i <= end; i++) { await new Promise(resolve => setTimeout(resolve, 1000)); yield i; } } let asyncIterator = generateSequence(1, 5); let result = await asyncIterator.next(); // {value: 1, done: false} result = await asyncIterator.next(); // {value: 2, done: false}
數組遍歷for (let x of iterable)與for (let x of asyncIterable)也是兩種基於pull理念的語法。
push系統
在push系統中,數據生產者決定何時向消費者推送數據。數據消費者不知道何時會收到數據更新。
Promise是一個push系統,它的數據生產者是Promise中封裝的異步邏輯,數據消費者則是then中的callback函數。生產者決定何時通知消費者。Promise生產者只能給消費者推送一次數據。
Rxjs的Observable也是一個push系統,它的生產者能給消費者推送多次數據。
對比總結
pull與push的特點對比總結如下:
生產者 | 消費者 | |
---|---|---|
pull | 被動。收到請求時返回數據 | 主動。決定何時請求數據 |
push | 主動。決定何時推送數據 | 被動。響應數據的更新 |
兩者的典型范例總結如下:
單結果 | 多結果 | |
---|---|---|
pull | Function | 數組遍歷、Iterator (Generator) |
push | Promise | Observable |
可以看出,數組等Iterable,與Observable是既對等又相反的關系。
RxJS為push模型提供了統一的抽象組合手段;與之相對應的,IxJS為pull模型提供了統一抽象組合手段。這兩個庫的API甚至能夠一一對應起來。
廣州vi設計公司 http://www.maiqicn.com 我的007辦公資源網 https://www.wode007.com
pull與push,陰與陽
如果從上面的微觀的視角來看Promise,Promise本身確實是一個典型的push模型。但是如果從宏觀的視角來看以下這段代碼:
// 在視圖控制器中的某段代碼: requestServer().then((data) => { view.update(data); }); // 等價於: const data = await requestServer(); view.update(data);
又何嘗不是一種pull模型呢?畢竟視圖控制器是數據消費者,它主動從服務器(數據生產者)請求數據並使用。
再換一個視角,如果從View的角度看,它被視圖控制器通知新數據的到來,View自己只能被動地對數據更新產生反應。這難道不是一種push模型嗎?
再換一個視角,上面的這段視圖控制器代碼是什么時候執行的呢?它總不可能在程序啟動的時候運行一次就完成任務了吧?這段代碼必定也處在某個事件響應函數中(比如某個按鈕的點擊事件回調),或者某個組件生命周期鈎子中(比如onMounted)。那么它作為一個事件響應函數,是不是必定處於一個push系統中?
再換一個視角,在requestServer中,向服務器發送請求的時候,底層需要通過DNS系統來解析域名,這個查詢過程是pull模型,即let ip = await resolveDNS('www.server.com');。
再換一個視角,在操作系統底層,當服務器響應從網絡中到來的時候,操作系統喚醒了對應的線程,執行后續的代碼。這個過程又是push模型。
可以看到,push與pull,它們作為編碼模型的兩種選擇,是相互競爭的,但是如果你站在不同的抽象層次上,總是能發現另一方的身影。這種“你中有我,我中有你,既相互競爭,又相互依存”的關系,像極了古代中國的哲學思想:陰陽