回調地獄與回調地獄解決方法


  • 什么是回調地獄(函數作為參數層層嵌套)

  • 什么是回調函數(一個函數作為參數需要依賴另一個函數執行調用)

  • 如何解決回調地獄

    • 保持你的代碼簡短(給函數取有意義的名字,見名知意,而非匿名函數,寫成一大坨)

    • 模塊化(函數封裝,打包,每個功能獨立,可以單獨的定義一個js文件Vue,react中通過import導入就是一種體現)

    • 處理每一個錯誤

    • 創建模塊時的一些經驗法則

    • 承諾/生成器/ES6等

  • Promises:編寫異步代碼的一種方式,它仍然以自頂向下的方式執行,並且由於鼓勵使用try / catch樣式錯誤處理而處理更多類型的錯誤

  • Generators:生成器讓你“暫停”單個函數,而不會暫停整個程序的狀態,但代碼要稍微復雜一些,以使代碼看起來像自上而下地執行

  • Async functions:異步函數是一個建議的ES7功能,它將以更高級別的語法進一步包裝生成器和繼承


demo

var sayhello = function(callback){
    setTimeout(function(){
        console.log("hello");
        return callback(null);
    },1000);
}
sayhello(function(err){
    console.log("xiaomi");
});
console.log("mobile phone");
//mobile phone
//hello
//Xiaomi

我現在利用上面的code 依次打印出xiaomi apple huawei 寫出來就是這樣的:

var sayhello = function(name, callback){
    setTimeout(function(){
        console.log("hello");
        console.log(name);
        return callback(null);
    },1000);
}
sayhello("xiaomi", function(err){
    sayhello("apple", function(err){
        sayhello("huawei", function(err){
            console.log("end");
        });
    });
});
console.log("mobile phone");

回調層層嵌套會帶來的問題。看起來太蠢了。

解決回調嵌套問題(ES6 promise)

var sayhello = function(name){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log("hello");
            console.log(name);
            resolve();
        },1000);
    });
}

sayhello("xiaomi").then(function(){
    console.log('frist');
}).then(function(){
    return sayhello("huawei");
    console.log('second');
}).then(function(){
    console.log('second');
}).then(function(){
    return sayhello("apple");
}).then(function(){
    console.log('end');
}).catch(function(err){
    console.log(err);
})
console.log("mobile phone");

 

ES6 co/yield方案

yield: Generator 函數是協程在 ES6 的實現,而yield是 Generator關鍵字, 異步操作需要暫停的地方,都用yield語句注明。
co: co 模塊是著名程序員 TJ Holowaychuk 於 2013 年 6 月發布的一個小工具,用於 Generator 函數的自動執行。
關於 co/yield詳細原理可參考: 
http://es6.ruanyifeng.com/#docs/generator-async

什么是Generator 函數?

Generator 函數是協程在 ES6 的實現,最大特點就是可以交出函數的執行權(即主動交出執行權,暫停執行)。
整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操作需要暫停的地方,都用yield語句注明。Generator 函數的執行方法如下。

function* gen() {
     yield console.log("test 1");
     yield console.log("test 2");
     yield console.log("test 3");
    return y;
}

var g = gen();
g.next()//“test 1”
g.next()//“test 2”

Generator 函數不同於普通函數的另一個地方,即執行它不會返回結果,返回的是指針對象。調用指針g的next方法,會移動內部指針(即執行異步任務的第一段),指向第一個遇到的yield語句,打印”test 1”並暫停,再次調用next會打印”test 2”暫停住,但不會打印”test 3”

Generator 函數可以暫停執行和恢復執行,這是它能封裝異步任務的根本原因

co模塊

Generator 就是一個異步操作的容器。它的自動執行需要一種機制,當異步操作有了結果,能夠自動交回執行權(即調用next)。
兩種方法可以做到這一點。
(1)回調函數。將異步操作包裝成 Thunk 函數,在回調函數里面交回執行權。
(2)Promise 對象。將異步操作包裝成 Promise 對象,用then方法交回執行權,因為resolve后會執行then方法。

co 模塊其實就是將兩種自動執行器(Thunk 函數和 Promise 對象),包裝成一個模塊。使用 co 的前提條件是,Generator 函數的yield命令后面,只能是 Thunk 函數或 Promise 對象。如果數組或對象的成員,全部都是 Promise 對象,也可以使用 co。co本身就是Promise 對象。

實質co流程可以理解為:

co 檢測 傳入函數 是否為Generator 。(是,則可以在yield處暫停執行)
co得到Generator函數返回指針,並使用next方法開始執行 Generator 。
第一個異步函數(Promise對象)有了結果,調用了resolve,co使用Promise 對象then的方法接管執行權接着調用next 執行Generator剩余部分,循環調用。

 使用

Generator 函數前面要加星號(*)
根據語法規范,yield* 的作用是代理 yield 表達式,將需要函數本身產生(yield)的值委托出去。yield* 后面跟一個生成器函數、或其他可迭代的對象(如一個數組、字符串、arguments對象)。yield* 表達式的返回值,就是其后面可迭代對象迭代完畢時的返回值。

var sayhello = function(name, ms){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log("hello");
            console.log(name);
            // if (error) return reject(error);
            resolve("helloworld");
        },ms);
    });
}

var gen = function *(){
    yield sayhello("xiaomi", 2000);
    console.log('frist');
    yield sayhello("huawei", 1000);
    console.log('second');
    yield sayhello("apple", 500);
    console.log('end');
}

console.log("mobile phone");
co(gen());
console.log("mobile end");

ES7 async/await 方案

async/await是es7的新標准,並且在node7.0中已經得到支持。
它就是 Generator 函數的語法糖,async函數就是將 Generator 函數的星號(*)替換成async,將yield替換成await,僅此而已。可以理解官方對co和Generator 封裝方案。
async函數定義如下

async function fn(){
    await sayhello();
    return 0;
}

關鍵字:async
async關鍵字用於修飾function,async函數的特征在於調用return返回的並不是一個普通的值,而是一個Promise對象,如果正常return了,則返回Promise.resolve(返回值),如果throw一個異常了,則返回Promise.reject(異常)。

關鍵字:await
await關鍵字只能在async函數中才能使用,也就是說你不能在任意地方使用await。await關鍵字后跟一個promise對象(你想要執行的異步操作),函數執行到await后會退出該函數,直到事件輪詢檢查到Promise有了狀態resolve或reject 才重新執行這個函數后面的內容。

它與直接使用Generator,具有以下優點:

內置執行器,所以不再需要co模塊了
async函數的await命令后面,可以是 Promise對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操作)
async函數的返回值是 Promise 對象

var sayhello = function(name, ms){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log("hello");
            console.log(name);
         if(name === "huawei")
                return reject(name);
             else
           return resolve("helloworld");

        }, ms);
    });
}

async function test() {
    try {
        console.log('frist');
        await sayhello("xiaomi", 2000);
        console.log('second');
        await sayhello("huawei", 1000);
        console.log('end');
        await sayhello("apple", 500);
    }
    catch (err) {
        console.log('err:'+err);
    }
};

test();

對錯誤的處理

await命令后面的 Promise 對象如果變為reject狀態,則reject的參數會被catch方法的回調函數接收到,而后續的await函數就不會繼續執行了。我們應該在將await語句放在try catch代碼塊中,如果有多個await命令,可以統一放在try…catch結構中,我們通過這種方式對發生的異步錯誤進行處理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM