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