2015年7月4日 星期六

Javascript使用Promise (bluebird) 操作說明

為什麼需要使用Promise?

在異步處理時非常實用,最常發生在讀取資料或大量運算時,javascript常使用callback去解決異步處理的問題。但使用callback在處理大量連續動作時,寫起來非常的難看也不是很好理解(個人感覺)。所以比較推崇使用promise去實踐這些動作。ES6本身有提供promise這個功能,但因為還需要Browser有支援才行。

當今天有三個動作必須依序處理,利用三個函式將動作包裝起來,再依序呼叫。
一般寫法如下:
function sleep(ms) {
  var unixtime_ms = new Date().getTime();
  while( new Date().getTime() < unixtime_ms + ms ) {}
}
function timerProcess ( delay ) {
    sleep( delay );
    console.log( "Process(" + delay + ") with " + new Date().getTime());
}
console.log( "Begin with " + new Date().getTime());
timerProcess( 3000 );
timerProcess( 2000 );
timerProcess( 1000 );
console.log( "End with " + new Date().getTime());
結果:
這種寫法會遇到一個問題,如果需要即時與UI互動時,會造成使用者在操作UI時,感覺被卡注的情況產生。

使用Callback函式寫法如下:

console.log( "Begin with " + new Date().getTime());
function timerCallback ( delay, callback ) {
    setTimeout( function () { callback( delay ) }, delay);
}

timerCallback( 3000, function( value ){
 console.log( "Process(" + value + ") with " + new Date().getTime());
 timerCallback( 2000, function( value ){
  console.log( "Process(" + value + ") with " + new Date().getTime());
  timerCallback( 1000, function( value ){
   console.log( "Process(" + value + ") with " + new Date().getTime());
  });
 });
});
console.log( "End with " + new Date().getTime());
結果:
你會發現程式會先跑到End,另外3個動作再依序處理完,這代表程式不會因為需要處理大量資訊而導致卡住的情況發生,當結果處理完再回傳並執行該繼續處理的步驟。

【Bluebird】
講了這兩個例子都不是這篇要講的bluebird promise所做的事情,如名稱,bluebird就是一個promise的第三方套件,也是一般大家比較常用的promise library。因為他提供了很多非常實用且方便的API供大家使用。

【常用API解說】
先寫一個基本的promise函式當作接下來講解時方便使用:此函式是代表動作需要處理的時間,並在處理完回傳該結果。
function timerPromisefy (delay) {
    return new Promise( function (resolve) {
        setTimeout( function () {
            resolve(delay);
        }, delay);
    });
}




Promise.all
所有promise的狀態都變成resolve時返回,但當其中一個狀態為reject時,則立即返回catch error。
var Promise = require("bluebird");

function timerPromisefy (delay) {
    return new Promise( function ( resolve, reject ) {
        setTimeout( function () {
         console.log( 'process was done ' + delay );
         ( delay < 64 )? resolve(delay): reject('reject');
        }, delay);
    });
}

Promise.all([
    timerPromisefy( 1 ),
    timerPromisefy( 4 ),
    timerPromisefy( 8 ),
    timerPromisefy( 16 )
]).then( function (value) {
    console.log(value);     // => [1 , 4 , 8 , 16]
}).catch ( function (error) {
    console.error(error); // => no error return
});

Promise.all([
    timerPromisefy( 32 ),
    timerPromisefy( 64 ),
    timerPromisefy( 128 ),
    timerPromisefy( 256 )
]).then( function (value) {
    console.log(value);     // => no result return
}).catch ( function (error) {
    console.error(error); // => reject
});


Promise.join】
類似於Promise.all,不同於所有promise結果返回後,根據不同結果執行下一動作。
var Promise = require("bluebird");

function timerPromisefy (delay) {
    return new Promise( function ( resolve, reject ) {
        setTimeout( function () {
         console.log( 'process was done ' + delay );
         ( delay < 64 )? resolve(delay): reject('reject');
        }, delay);
    });
}

var join = Promise.join;
join(timerPromisefy(1), timerPromisefy(4), timerPromisefy(8),
    function(agrv1, agrv2, agrv3) {
     // agrv1 = 1, agrv2 = 4, agrv3 = 8
     return timerPromisefy( agrv1*1 + agrv2*2 + agrv3*3);
}).then( function( value ){
 console.log( value ); // => 33
}).catch( function( error ){
 console.error( error );
});

Promise.props】
回傳物件中所有的動作,可依據定義的名稱拿值
var propsFunc = {
 timeFunc1: timerPromisefy(1),
 timeFunc32: timerPromisefy(32),
 timeFunc64: timerPromisefy(64),
 timeFunc128: timerPromisefy(128)
}
Promise.props( propsFunc ).then(function(result) {
    console.log(result.timeFunc1, result.timeFunc128, result.timeFunc64);
});




Promise.race
回傳第一個執行完成的promise(resolve或者reject其中一個處發便返回)
var Promise = require("bluebird");

function timerPromisefy (delay) {
    return new Promise( function ( resolve, reject ) {
        setTimeout( function () {
         console.log( 'process was done ' + delay );
         ( delay > 64 )? resolve(delay): reject('reject');
        }, delay);
    });
}

Promise.race([
    timerPromisefy( 128 ),
    timerPromisefy( 1024 ),
    timerPromisefy( 512 ),
    timerPromisefy( 256 )
]).then( function (value) {
    console.log(value);     // => 128
}).catch( function( error ){
 console.error( error ); // no error return
});

Promise.race([
    timerPromisefy( 1 ),
    timerPromisefy( 32 ),
    timerPromisefy( 64 ),
    timerPromisefy( 128 )
]).then( function (value) {
    console.log(value);     // no result return
}).catch( function( error ){
 console.error( error ); // => reject









Promise.any
回傳第一個執行完成的promise(不包含reject)
var Promise = require("bluebird");

function timerPromisefy (delay) {
    return new Promise( function ( resolve, reject ) {
        setTimeout( function () {
         console.log( 'process was done ' + delay );
         ( delay > 64 )? resolve(delay): reject('reject');
        }, delay);
    });
}

Promise.any([
    timerPromisefy( 128 ),
    timerPromisefy( 1024 ),
    timerPromisefy( 512 ),
    timerPromisefy( 256 )
]).then( function (value) {
    console.log(value);     // => 128
}).catch( function( error ){
 console.error( error ); // no error return
});

Promise.any([
    timerPromisefy( 1 ),
    timerPromisefy( 32 ),
    timerPromisefy( 64 ),
    timerPromisefy( 128 )
]).then( function (value) {
    console.log(value);     // => 128
}).catch( function( error ){
 console.error( error ); // => no error return
});

Promise.some
回傳最快執行完成的前2個結果
Promise.some([
    timerPromisefy( 1 ),
    timerPromisefy( 32 ),
    timerPromisefy( 64 ),
    timerPromisefy( 128 )
], 2).spread(function(first, second) {
    console.log(first, second);// => 1, 32
});


Promise.map
做的事情其實跟Promise.all一樣,差別在於,如果指是call同一函式,只是參數不同的話,可以有更簡潔的寫法。
var Promise = require("bluebird");

function timerPromisefy (delay) {
    return new Promise( function ( resolve, reject ) {
        setTimeout( function () {
         console.log( 'process was done ' + delay );
         ( delay < 64 )? resolve(delay): reject('reject');
        }, delay);
    });
}

//Using Promise.all
var params = [1, 4, 8, 16];
var promises = [];
for (var i = 0; i < params.length; ++i) {
    promises.push(timerPromisefy(params[i]));
}
Promise.all(promises).then(function() {
    console.log("done");
});

// Using Promise.map:
var params = [1, 4, 8, 16];
Promise.map(params, function(time) {
    return timerPromisefy(time);
}).then( function (value) {
    console.log("done");  
});


Promise.reduce
跑完所有promise,並將所有回傳值相加。
var initialValue = 0;
Promise.reduce([1, 32, 64, 128], function(total, time) {
    return timerPromisefy(time).then(function(value) {
        return total + value;
    });
}, initialValue).then(function(total) {
    console.log( total ); //255
});
Promise.spread
spread接收Array的結果
Promise.delay(3500).then(function() {
   return [timerPromisefy(1),
           timerPromisefy(32)] ;
}).spread(function(file1text, file2text) {
    if (file1text !== file2text) {
        console.log("files are equal");
    }
    else {
        console.log("files are not equal");
    }
});
Promise.timeout
處理時間超過timeout便通知catch
var thTimeout = 100;
timerPromisefy(1024).timeout(thTimeout).then( function(){
 console.log("process less " + thTimeout + "ms");
}).catch(Promise.TimeoutError, function(e) {
    console.log("process over " + thTimeout + "ms");
});
Promise.delay
處理完延遲2000ms再處理接下來的動作
var delayTime = 2000;
timerPromisefy(1024).delay(delayTime).then( function(){
 console.log("return result after " + delayTime + "ms");
});
Promise.call
當第一批處理完的結果(Array),呼叫 'some' 的函式過濾結果。
some ==> Array.prototype.some() 原生函式
var times = [1, 32, 64, 128];
Promise.map(times, function(time) {
    return timerPromisefy(time);
}).call("some", function(stat) {
    return stat > 100;
}).then(function(res) {
    console.log(res);  // => true
}).catch();


陸續新增...


1 則留言:

  1. 不好意思灰底白字真的很難閱讀..

    回覆刪除