跳到主要內容

Polling with JQuery deferred (en)

Polling with JQuery deferred (en)

Problem

Imagine you want to get some web pages via JQuery Ajax, but without knowing how many pages until you actually fetch them. In such case, you can’t aggregate deferred objects before fetching. Available solution is polling pages instead. That is, fetch a page and see if more pages to go. If so, continue to retrieve another one, otherwise stop polling.

Let’s assume an API for polling page. e.g.

http://example.com/api/page?p=n`

and request it several time with consecutive n. i.e.

var api = 'http://example.com/api/page';
$.get(api, { p : 0 })
.then(function(data) {
    if(is_more(data)) {
        return $.get(api, {p : 1});
    }
    return data;
})
.then(...)
;

Now you see the problem I’m gonna shoot it - how to resolve the last then() with polled pages?

Objective

Prototype:

/**
 * @param api  URL for retrieving page.
 * @param next Function for advancing parameter for next retrieving which returns
 *             false if no more page.
 * @param fold Function returns accumulating pages.
 */
poll_pages(api, next, fold)
{ ... }

Usage:

var api = 'http://example.com/api/page';

// Generate iterator for polling
function make_next(initial) { 
    return function(data, status, xhr) {
        return ( xhr.status == 200 ) ? 
            { n : initial++ } :
            null ;
    }; 
};

// Closure for accumulating pages
function make_fold() {
    var pages = [];
    return function(obj) {
        if(obj)
            pages.push(obj);
        return pages;
    };
};

poll_pages(api, make_next(0), make_fold())
    .then(function(all_pages) {
        console.log(all_pages);
    });

Ver 0.1

The key idea is creating another deferred object to guard polling operation.

function poll_pages(api, next, fold) {
    // Our guardian
    var deferred = $.Deferred();
    function polling(data, status, xhr) {
        var next_param = next(data, status, xhr);
        if( xhr.status >= 400 ) {
            // Error handling
            deferred.reject(fold());
        } else if( next_param ) {
            fold(data);
            // Recursive polling
            return $.get(api, next_param).then(polling);
        } else {
            // No more pages
            deferred.resolve(fold());
        }
        return deferred.promise();
    }
    return $.get(api, next()).then(polling);
}

Note Above code hadn’t been tested. Please refer to following section for tested implementation.

Ver 0.2 (Generalization)

For generalization, we can decouple terminating condition from poll_pages(), and put it in into next(). As such, clients can craft the make_next() whatever they want. Though, a simple protocol is required in between next() and poll_general(), for differentiating error, termination, and continuation:

  1. If some error occurred, then return false.
  2. If an expected end of iteration, then return true.
  3. Otherwise return deferred object for polling.

Prototype:

/**
 * @param next Function returns deferred object or boolean (for stop polling).
 * @param fold Function returns accumulated data.
 * @param this_arg Context for the `next' function.
 */
poll_general(next, fold, this_arg)
{ ... }

Usage:

function make_next(api, initial) {
    return function(data, status, xhr) {
        // error
        if(xhr && xhr.status >= 400) {
            return false;
        }
        // termination
        if(initial > 2) {
            return true;
        }
        // continue
        if (arguments.length != 0) {
            initial += 1;
        }
        return $.get(api, { p: initial });
    };
}

function make_fold() {
    var accumulated = []
    return function(data, status, xhr) {
        if(data) 
            accumulated.push(data);
        return accumulated;
    }
}

poll_general(make_next('http://fiddle.jshell.net',0), make_fold())
.then(function(accumulated){
    console.log(accumulated);
});

Implementation:

function poll_general(next, fold, this_arg) {
    var deferred = $.Deferred();
    function polling() {
        // Now we resolve/reject deferred per next()
        var iteration = next.apply(this_arg, arguments);
        if(iteration && iteration.then) {
            fold.apply(null, arguments);
            return iteration.then(polling);
        } else if (iteration) {
            deferred.resolve(fold());
        } else {
            deferred.reject(fold());
        }
        return deferred.promise();
    }
    return next().then(polling);
}

Live demo in JSFiddle.

Note In above example, we stop polling after get 2 pages due to we are polling the same page in fact.


Written with StackEdit.

留言

這個網誌中的熱門文章

得利油漆色卡編碼方式

得利油漆色卡編碼方式 類似 Munsell 色彩系統 ,編碼方式為 HUE LRV/CHROMA 例如 10GY 61/449 ( 色卡 ) 編碼數值 描述 10GY hue ,色輪上從 Y(ellow) 到 G(reen) 區分為 0 ~ 99 ,數值越小越靠近 Y,越大越靠近 G 61 LRV (Light Reflectance Value) 塗料反射光源的比率,數值從 0% ~ 100% ,越高越亮,反之越暗,也可理解為明度 449 chroma 可理解為彩度,數值沒有上限,越高顏色純度 (濃度) 越高 取決於測量儀器,對應至 RGB 並不保證視覺感受相同。 參考資料: 色卡對照網站 e-paint.co.uk Written with StackEdit .

UTF8 與 Unicode 的轉換 (C++)

UTF8 與 Unicode 的轉換 (C++) 先釐清一下這兩者的性質 Unicode: 為世界上所有的文字系統制訂的標準,基本上就是給每個字(letter)一個編號 UTF-8: 為 unicode 的編號制定一個數位編碼方法 UTF-8 是一個長度介於 1~6 byte 的編碼,將 unicode 編號 (code point) 分為六個區間如下表 1 Bits First code point Last code point Bytes Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 7 U+0000 U+007F 1 0xxxxxxx 11 U+0080 U+07FF 2 110xxxxx 10xxxxxx 16 U+0800 U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx 21 U+10000 U+1FFFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 26 U+200000 U+3FFFFFF 5 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 31 U+4000000 U+7FFFFFFF 6 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 觀察上面的表應該可以發現 除了 7 bits 的區間外,第一個 byte 開頭連續 1 的個數就是長度,例如 110XXXXX 就是 2 byte 長,而 1110xxxx 就是 3 byte 除了第一個 byte 外,之後的 byte 前兩個 bit 一定是 10 開頭,這樣的好處在於確立了編碼的 self-synchronizeing,意即當編碼為多個 byte 時,任取一個 byte 無法正常解碼。 Note 第一點中的例外 (7 bits) 是為了與 ASCII 的相容性,而第二點會影響到 code point 至 UTF-8 的轉換。 為了與 UTF-16 的相容性,在 R

C++17 新功能 try_emplace

C++17 新功能 try_emplace 回顧 emplace 大家的好朋友 Standard Template Library (STL) 容器提供如 push_back , insert 等介面,讓我們塞東西進去; C++11 之後,新增了 emplace 系列的介面,如 std::vector::emplace_back , std::map::emplace 等,差異在於 emplace 是在容器內 in-place 直接建構新元素,而不像 push_back 在傳遞參數前建構,下面用實例來說明: struct Value { // ctor1 Value ( int size ) : array ( new char [ size ] ) , size ( size ) { printf ( "ctor1: %d\n" , size ) ; } // ctor2 Value ( const Value & v ) : array ( new char [ v . size ] ) , size ( v . size ) { printf ( "ctor2: %d\n" , size ) ; memcpy ( array . get ( ) , v . array . get ( ) , size ) ; } private : std :: unique_ptr < char [ ] > array ; int size = 0 ; } ; struct Value 定義了自訂建構子 (ctor1),以指定大小 size 配置陣列,複製建構子 (ctor2) 則會配置與來源相同大小及內容的陣列,為了方便觀察加了一些 printf 。當我們如下使用 std::vector::push_back 時 std :: vector < Value > v ; v . push_back ( Value ( 2048 ) ) ; 首先 Value 會先呼叫 ctor1,傳給 push_ba