天天看點

[Effective JavaScript 筆記]第63條:當心丢棄錯誤

管理異步程式設計的一個是錯誤處理。同步代碼中隻要使用try語句塊包裝一段代碼很容易一下子處理所有的錯誤。

try{
    f();
    g();
    h();
} catch(e){
    //這裡用來下得出現的錯誤
}
           

try語句塊

但對于異步的代碼,多步的處理通常會被分隔到事件隊列的單獨輪次中,是以,不可能将它們包裝在一個try語句塊中。事實上異步的API甚至根本不可能抛出異常,因為,當一個異步的錯誤發生時,沒有一個明顯的執行上下文來抛出異常!相反,異步的API傾向于将錯誤表示為回調函數的特定參數,或使用一個附加的錯誤處理回調函數。例如,一個涉及下載下傳檔案的異步API可能會有一個額外的回調函數來處理網絡錯誤。

downloadAsync('http://cnblogs.com/wengxuesong',function(text){
    console.log('file contents:'+text);
},function(error){
    console.log('error:'+error);
});
           

如果下載下傳多個檔案,可以像62條講的,使用回調函數嵌套起來。

downloadAsync('a.txt',function(a){
    downloadAsync('b.txt',function(b){
        downloadAsync('c.txt',function(c){
            console.log('Contents:'+a+b+c);
        },function(error){
            console.log('error:'+error);
        });
    },function(error){
        console.log('error:'+error);
    });
},function(error){
    console.log('error:'+error);
});
           

上面代碼上,每一步的處理都使用了相同的錯誤處理邏輯,然而我們在多個地方重複了相同的代碼。在程式設計領域裡,應該努力堅持避免重複代碼。通過共享作用域中定義一個錯誤處理的函數,将重複代碼抽象出來。

function onError(error){
    console.log('Error:'+error);
}
downloadAsync('a.txt',function(a){
    downloadAsync('b.txt',function(b){
        downloadAsync('c.txt',function(c){
            console.log('Contents:'+a+b+c);
        },onError);
    },onError);
},onError);
           

如果使用工具函數downloadAllAsync将多個步驟合并到一個複合的操作中,那麼,隻需要提供一個錯誤處理的回調函數。

downloadAllAsync(['a.txt','b.txt','c.txt'],function(abc){
    console.log('Contents:'+abc[0]+abc[1]+abc[2]);
},function(error){
   console.log('Error:'+error); 
});
           

回調函數錯誤參數

另一種錯誤處理API的風格是Node.js平台使用的。該風格隻需要一個回調函數,該回調函數的第一個參數如果有錯誤發生就表示一個錯誤。否則就是一個假值,比如null。對于這類API,我們可以定義一個通用的錯誤處理函數,需要使用if語句來控制每個回調函數。

function onError(error){
    console.log('Error:'+error);
}
downloadAsync('a.txt',function(error,a){
    if(error){
        onError(error);
        return;
    }
    downloadAsync('b.txt',function(error,b){
        if(error){
            onError(error);
            return;
        }
        downloadAsync('c.txt',function(error,c){
            if(error){
                onError(error);
                return;
            }
            console.log('Contents:'+a+b+c);
        });
    });
});
           

程式員通常會放棄if語句而使用大括号結構跨越多行的約定,以使得錯誤處理更簡潔、更集中。

function onError(error){
    console.log('Error:'+error);
}
downloadAsync('a.txt',function(error,a){
    if(error)return onError(error);
    downloadAsync('b.txt',function(error,b){
        if(error)return onError(error);
        downloadAsync('c.txt',function(error,c){
            if(error)return onError(error);
            console.log('Contents:'+a+b+c);
        });
    });
});
           

也可以使用一個抽象合并步驟來幫助消除重複

var filenames=['a.txt','b.txt','c.txt'];
downloadAllAsync(filenames,function(error,abc){
    if(error){
        console.log('Error:'+error);
        return;
    }
    console.log('Contents:'+abc[0]+abc[1]+abc[2]);
});
           

try...catch語句和在異步API中典型的錯誤處理邏輯的一個實際差異是,try語句使得定義一個"捕獲所有"的邏輯很容易導緻程式員難以忘懷整個代碼區的錯誤處理。而上面給出的異步API,非常容易忘記在程序的任意一步提供錯誤處理。這将導緻錯誤被丢棄。忽視錯誤處理的程式會令使用者非常沮喪:應用程式出錯時沒有任何的回報。類似的,預設的錯誤不好調試。因為沒有提供問題來源的線索。最好是做好防禦,即使用異步API需要警惕,確定明确地處理所有的錯誤狀态條件。

提示

  • 通過編寫共享的錯誤處理函數來避免複制和粘貼錯誤處理代碼
  • 確定明确地處理所有的錯誤條件以避免丢棄錯誤

版權聲明

翻譯的文章,版權歸原作者所有,隻用于交流與學習的目的。

原創文章,版權歸作者所有,非商業轉載請注明出處,并保留原文的完整連結。

繼續閱讀