2019年4月5日

初探JavaScript Promise物件

近年來,在許多JavaScript程式碼中,紛紛使用了Promise物件來處理非同步工作。而其最為人所知的好處,就是在語法上避免了callback hell的問題。

Promise基本使用

1. 建立Promise物件,以執行非同步工作:

Promise的建構式接受一個function型別的參數,表示你將要執行的非同步"工作";此外,它本身有兩個function型別的參數,先後分別代表工作成功或失敗時,需要被呼叫的callback函式(如下例中,分別命名為resolvereject)。

var myWork = new Promise(function(resolve, reject) {
    // 做些事情 ... 
    // 此處假設我們的工作是取一個亂數,
    // 若大於0.5則表示工作成功;反之則表示工作失敗
    var randomNumber = Math.random();
    var workResult = randomNumber > 0.5;
    
    //使用Timeout來模擬這個工作需要一段時間的情境
    setTimeout(function(){
        if (workResult) {
            //此處可以傳一個參數,作為稍後成功處理時使用
            resolve({data:randomNumber});
        }
        else {
            //此處可以傳一個參數,作為稍後失敗處理時使用
            reject({error:"The random number is less than 0.5!", randomNumber:randomNumber});    
        }    
    }, 3000);
});

你可以傳一個參數給resolve函式或reject函式,在稍後提到的then方法中,都可以取得。

2. 使用then方法,執行成功或失敗處理:

then方法則接受兩個function型別的參數,第一個是工作成功時的處理;第二個是工作失敗時的處理。

myWork.then(function(data){
    //成功時的處理
    console.log("My work is successfully done! data:", data);
}, function(error){
    //失敗時的處理
    console.log("Some errors occurred! error:", error);
});

使用catch處理失敗

在上例使用then方法時,也可以省略第二個參數,也就是工作失敗時的處理,而改用catch方法處理:

myWork.then(function(){
    //成功時的處理  
    console.log("My work is successfully done!");
}).catch(function(){
    //失敗時的處理,使用catch方法
    console.log("Some errors occurred! Catch it!");
});

注意,如果呼叫then方法時,有使用第二個參數處理錯誤,那麼接在後面的catch就不會被執行了。

Promise的串接

在then方法傳入的function型別的參數中,可以回傳一個新的Promise物件,執行下一個工作:

myWork.then(function(){
    //成功時的處理  
    console.log("My work is successfully done!");
    
    //第二個工作
    return new Promise(/* ... */);    
}).then(function(){
    //第二個工作成功時的處理
    console.log("My second work is successfully done!");    
}).catch(function(){
    //失敗時的處理,無論是第一個還是第二個工作失敗,都會落入這裡。
    console.log("Some errors occurred! Catch it!");
});

Promise的應用

以下以XMLHttpRequest為例,示範使用Promise與沒有使用Promise的差異。

沒有使用 Promise

function makeRequest(path, onsuccess, onerror) {
    var req = new XMLHttpRequest();
    req.addEventListener("load", function(){
        if(this.status == "200") {
            //request OK
            onsuccess({data:this.responseText, url:this.responseURL});
        }
        else {
            //request failed
            onerror({error:"The status code is not 200!", status:this.status, url:this.responseURL});
        }
    });
    req.open("GET", path);
    req.send();
}
makeRequest("/path_a", function(data){
    //成功時的處理
    
    //下一個Request
    makeRequest("/path_b", function(data){
        //成功時的處理

        /*
        //再下一個請求...?!
        makeRequest("/path_c", ...
            //成功時的處理...
            //看來要陷入了callback hell了,還是先就此打住吧!
        });
        */
    },function(error){
        //失敗時的處理

    });
},function(error){
    //失敗時的處理

});

使用Promise

function makePromiseRequest(path) {
    //使用Promise將原本的工作包裝起來
    new Promise(function(resolve, reject) {        
        //以下內容如同原本的makeRequest,
        //惟注意成功與失敗的處理函式,改用resolve與reject
        var req = new XMLHttpRequest();
        req.addEventListener("load", function(){
            if(this.status == "200") {
                //request OK
                resolve({data:this.responseText, url:this.responseURL});
            }
            else {
                //request failed
                reject({error:"The status code is not 200!", status:this.status, url:this.responseURL});
            }
        });
        req.open("GET", path);
        req.send();        
    });
}
makePromiseRequest("/path_a").then(function(data){
    //成功時的處理
    
    //下一個Request
    return makePromiseRequest("/path_b");
}).then(function(data){
    //成功時的處理
    
    //繼續下一個Request!? Easy!
    return makePromiseRequest("/path_c");
}).then(function(data){
    //成功時的處理
    
    //...
}).catch(function(error){
    //失敗時的處理,只要某次Request失敗,即會若入這裡;
    //你可以由當初你自行傳入的error物件,來判別發生了什麼錯誤,以及該如何處理!
    console.log("Some errors occurred! Catch it!", error);
});

Reference

使用 Promise - JavaScript | MDN

Promise - JavaScript | MDN

沒有留言:

張貼留言