摘要:方法完成回調注冊模式下,對象通過方法調用,注冊完成態和失敗態的回調函數。這些回調函數組成一個回調隊列,處理的值。調用實例的方法,能使注冊的回調隊列中的回調函數依次執行。
之前寫了一篇關于ES6原生Promise的文章。近期又讀樸靈的《深入淺出Node》,里面介紹了一個Promise/Deferred模式。
Promise是解決異步問題的利器。它其實是一種模式。Promise有三種狀態,未完成態、完成態、失敗態,相信大家一定不陌生,Promise對象允許使用.then的形式,將回調放到IO操作等異步方法的主體之外,使代碼優美不少。
下面我結合《深入淺出Node》,介紹一下如何用ES5實現Promise/Deferred模式。相信研究完該實現代碼之后,我們會對Promise的理解更進一步。
Promisethen方法完成回調注冊
Promise/Deferred模式下,Promise對象通過then方法調用,注冊完成態和失敗態的回調函數。
由于then方法支持鏈式回調,因此then方法的返回值一定也是Promise對象,我們在此簡單的返回自身,也就是this。
那么一定有人要問了:then中的回調函數,可能返回一個新的Promise對象,此后的then調用是否是在新的Promise對象上調用的呢?
答案是:不一定。
這個問題其實困擾我很久,直到看了Promise的實現代碼我才想明白。其實then方法的調用,只不過是注冊了完成態和失敗態下的回調函數而已。這些回調函數組成一個回調隊列,處理resolve的值。
Promise構造函數注冊回調隊列
Promise構造函數,給每個實例一個queue屬性,將then方法注冊的回調隊列,保存在Promise實例的回調隊列中。
代碼
var Promise = function(){ this.queue = []; } Promise.prototype.then = function(fulfilledHandler, unfulfilledHandler){ var handler = {}; if (typeof fulfilledHandler === "function"){ handler.fulfilled = fulfilledHandler; } if (typeof unfulfilledHandler === "function"){ handler.unfulfilled = unfulfilledHandler; } this.queue.push(handler); return this; }
我們看到,Promise的代碼很簡單,只是通過then方法將一系列的回調函數push到隊列中而已。Promise實例暴露給用戶的也只有一個then方法。
這樣我們就可以這樣調用了:
promise.then(fulfilledFunc1, unfulfilledFunc1) .then(fulfilledFunc2, unfulfilledFunc2) .then(fulfilledFunc3, unfulfilledFunc3)
那么如何進行狀態轉換呢?下面我就來講一下帶有resolve方法(reject方法同理,下面均以resolve舉例)的Deferred。
DeferredDeferred實例決定Promise實例的狀態
每個Deferred實例的對應一個Promise實例。調用Deferred實例的resolve方法,能使Promise注冊的回調隊列中的回調函數依次執行。
先寫部分代碼:
var Deferred = function(){ this.promise = new Promise(); } Deferred.protoype.resolve = function(val){ var handler, value = val; while(handler = this.promise.queue.shift()){ if (handler && handler.fulfilled){ value = handler.fulfiller(value) && value; } } }
這樣我們就能使用Deferred實例返回Promise實例,并且使用Deferred實例的resolve方法來觸發Promise實例的完成態回調,并且將上一個回調如果有返回值,我們將該返回值作為新的resolve值傳遞給后面的回調。
處理回調方法返回的Promise實例
根據Promise模式,回調函數返回Promise實例時,下一個then()中的回調處理的是新的Promise實例。
在之前的代碼實現中,then方法注冊了一系列的回調函數,這些回調函數應該處理新的promise實例。這里我們用了一個小技巧,見代碼:
Deferred.protoype.resolve = function(val){ var handler, value = val; while(handler = this.promise.queue.shift()){ if (handler && handler.fulfilled){ value = handler.fulfiller(value) && value; // 修改之處在這里: if (value && value.isPromise){ value.queue = this.promise.queue; // 最后再加一個小技巧 this.promise = value; return; } } } }
我們將返回promise實例之后的回調列表原封不動的注冊到返回的promise中,這樣就保證之前then注冊的回調隊列能繼續調用。最后的小技巧可以使舊的deferred實例對應新的promise實例,這樣可以繼續使用deferred.resolve方法。
為了判斷實例是否是Promise實例,這里簡單的修改Promise構造函數:
var Promise = function(){ this.queue = []; this.isPromise = true; }
封裝callback方法用于異步調用
Promise之所以是解決異步的利器,一方面是then方法的鏈式調用,一方面也是因為resolve方法可以異步調用,觸發回調隊列。
由于以NodeJS為標志的異步方法其回調函數類似于這樣:
asyncFunction(param, function(err, data){ // do something... });
我們可以封裝一個自己的callback方法,用于異步觸發resolve方法。
Deferred.prototype.callback = function(err, data){ if (err){ this.reject(err); } this.resolve(data); }
此后我們可以這樣promisify一個異步函數:
var async = function(param){ var defer = new Deferred(); var args = Array.prototype.silce.call(arguments); args.push(defer.callback); asyncFunc.apply(null, args); return defer.promise; }Promisify
由上面的promisify思路,我們寫一個更一般化的promisify函數:
var promisify = function(method){ return function(){ var defer = new Deferred(); var args = Array.prototype.silce.call(arguments, 1); args.push(defer.callback); asyncFunc.apply(null, args); return defer.promise; } }
舉一個Node中文件操作的例子:
readFile = promisify(fs.readFile); readFile("file1.txt", "utf8").then(function(file1){ return readFile(file1.trim(), "utf8"); }).then(function(file2){ console.log(file2); })
倆字:優雅。
結束文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80181.html
摘要:隨著前端的發展,異步這個詞真是越來越常見了。真正帶來革命性改變的是規范。借助,我們可以這樣完成異步任務好棒寫起來像同步處理的函數一樣別著急,少年??偨Y以上就是筆者總結的幾種異步編程模式。 隨著前端的發展,異步這個詞真是越來越常見了。假設我們現在有這么一個異步任務: 向服務器發起數次請求,每次請求的結果作為下次請求的參數。 來看看我們都有哪些處理方法: Callbacks ...
摘要:以前其實寫過一篇和的對比但是后來發現里面有不少謬誤所以一直惦記著糾正一下之前的錯誤尤其關于中間件部分的對比這里的就拿更加簡單的代替的執行流程通常我們都說的中間件模型是線性的也就是一個一個往下執行的如下圖這么說當然是沒錯的但是當我們執行下面代 以前其實寫過一篇express和koa的對比, 但是后來發現里面有不少謬誤. 所以一直惦記著糾正一下之前的錯誤, 尤其關于中間件部分的對比. 這里...
摘要:前言前幾天在理解的事件環機制中引發了我對瀏覽器里的好奇。接下來理解瀏覽器中的,先看一張圖堆和棧堆是用戶主動請求而劃分出來的內存區域,比如你,就是將一個對象存入堆中,可以理解為存對象。廢話不多說,直接上圖個人理解。參考資料運行機制詳解再談 前言 前幾天在理解node的事件環機制中引發了我對瀏覽器里Event Loop的好奇。我們都知道javascript是單線程的,任務是需要一個一個按順...
摘要:一直以來,對的執行機制都是模棱兩可,知道今天看了文章這一次,徹底弄懂執行機制和的規范和實現,才對的執行機制有了深入的理解,下面是我的學習總結。個要點是單線程語言是的執行機制,為了實現主線程的不阻塞,就這么誕生了。 一直以來,對JS的執行機制都是模棱兩可,知道今天看了文章—《這一次,徹底弄懂JavaScript執行機制》和《Event Loop的規范和實現》,才對JS的執行機制有了深入的...
摘要:眾所周知和都屬于上述異步任務的一種那到底為什么和會有順序之分這就是我想分析總結的問題所在了和的作用是為了讓瀏覽器能夠從內部獲取的內容并確保執行棧能夠順序進行。只要執行棧沒有其他在執行,在每個結束時,隊列就會在回調后處理。 前言 我是在做前端面試題中看到了setTimeout和Promise的比較,然后第一次看到了microtask和macrotask的概念,在閱讀了一些文章之后發現沒有...
閱讀 2847·2021-09-10 10:51
閱讀 2215·2021-09-02 15:21
閱讀 3206·2019-08-30 15:44
閱讀 869·2019-08-29 18:34
閱讀 1652·2019-08-29 13:15
閱讀 3322·2019-08-26 11:37
閱讀 2697·2019-08-26 10:46
閱讀 1107·2019-08-26 10:26