異步編程中的異常處理

此文爲轉載的,感謝原文作者——syaning!

一、引言

一般情況下,我們會使用try..catch..來進行異常處理,例如:

function sync() {
    throw new Error('sync error');
}

try {
    sync();
} catch (err) {
    console.log('error caught:', err.message);
}

// error caught: sync error

然而對於異步函數,try..catch..就無法得到想要的效果了,例如:

function async() {
    setTimeout(function() {
        throw new Error('async error');
    }, 1000);
}

try {
    async();
} catch (err) {
    console.log('error caught:', err.message);
}

// Uncaught Error: async error

這是因爲異步調用是立即返回的,因此當發生異常的時候,已經脫離了try..catch..的上下文了,所以異常無法被捕獲。

二、實現

下面來介紹異步編程下幾種常用的異常處理方法。

1. callback

通過回調函數可以比較方便地進行異常處理,例如:

function async(callback, errback) {
    setTimeout(function() {
        var rand = Math.random();
        if (rand < 0.5) {
            errback('async error');
        } else {
            callback(rand);
        }
    }, 1000);
}

async(function(result) {
    console.log('scucess:', result);
}, function(err) {
    console.log('fail:', err);
});

這裏通過setTimeout來模擬實現了一個異步函數async,該函數可能調用成功,也可能調用失敗拋出異常。async接收兩個參數,當調用成功時callback會被執行,當拋出異常時errback會被執行。

有時候爲了方便,也會將callback和errback合併爲一個回調函數,這也是Node風格回調處理。代碼如下:

function async(callback) {
    setTimeout(function() {
        var rand = Math.random();
        if (rand < 0.5) {
            callback('async error');
        } else {
            callback(null, rand);
        }
    }, 1000);
}

async(function(err, result) {
    if (err) {
        console.log('fail:', err);
    } else {
        console.log('success:', result);
    }
});

這裏err作爲回調函數的第一個參數,如果async調用成功,則err爲一個falsy值(可以是null或undefined等,在該例子中使用null);如果async調用失敗,則err爲拋出的異常。當調用回調函數的時候,先判斷err是否爲falsy。如果爲falsy,則執行成功的回調;否則進行異常處理(這裏原文是錯誤的,剛好邏輯相反了,我估計是作者不小心寫錯了)

然而在多異步串行的情況下,使用回調函數的方式,就會出現所謂的回調金字塔問題,代碼可讀性也會大打折扣。例如:

function async(callback) {
    setTimeout(function() {
        var rand = Math.random();
        if (rand < 0.2) {
            callback('async error');
        } else {
            callback(null, rand);
        }
    }, 1000);
}

async(function(err, result) {
    if (err) {
        console.log('fail:', err);
    } else {
        console.log('success:', result);
        async(function(err, result) {
            if (err) {
                console.log('fail:', err);
            } else {
                console.log('success:', result);
                async(function(err, result) {
                    if (err) {
                        console.log('fail:', err);
                    } else {
                        console.log('success:', result);
                    }
                });
            }
        });
    }
});

在這裏,回調函數一層嵌一層,而且每一層都要判讀是否出錯。不僅代碼可讀性差,維護起來也非常不方便。

2. promise

上面的例子,使用promise的話,代碼如下:

function async() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            var rand = Math.random();
            if (rand < 0.5) {
                reject('async error');
            } else {
                resolve(rand);
            }
        }, 1000);
    });
}

async().then(function(result) {
    console.log('success:', result);
}, function(err) {
    console.log('fail:', err);
});

或者使用catch的方式,代碼如下:

function async() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            var rand = Math.random();
            if (rand < 0.5) {
                reject('async error');
            } else {
                resolve(rand);
            }
        }, 1000);
    });
}

async().then(function(result) {
    console.log('success:', result);
}).catch(function(err) {
    console.log('fail:', err);
});

使用promise的好處是執行流程直觀,但是理解起來比回調函數要麻煩一些。

對於多異步操作串行的問題,使用promise的方式會使得代碼簡潔優雅,可讀性也很強。代碼如下:

function async() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            var rand = Math.random();
            if (rand < 0.2) {
                reject('async error');
            } else {
                resolve(rand);
            }
        }, 1000);
    });
}

function onResolved(result) {
    console.log('success:', result);
}

function onRejected(err) {
    console.log('fail:', err);
}

async().then(onResolved)
.then(async)
.then(onResolved)
.then(async)
.then(onResolved)
.catch(onRejected);

3. domain

在Node中,有一個domain模塊(在io.js中該模塊已經標記爲deprecated),可以用來處理異步操作異常。示例代碼如下:

var domain = require('domain');

function async(callback) {
    setTimeout(function() {
        var rand = Math.random();
        if (rand < 0.5) {
            throw 'async error';
        } else {
            callback(rand);
        }
    }, 1000);
}

var d = domain.create();
d.on('error', function(err) {
    console.log('fail:', err);
});
d.run(function() {
    async(function(result) {
        console.log('success:', result);
    });
});

Domain類繼承自EventEmitter,所以它本質上就是一個事件發生器。

發佈了45 篇原創文章 · 獲贊 57 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章