-
什么是回調地獄(函數作為參數層層嵌套)
-
什么是回調函數(一個函數作為參數需要依賴另一個函數執行調用)
-
如何解決回調地獄
-
保持你的代碼簡短(給函數取有意義的名字,見名知意,而非匿名函數,寫成一大坨)
-
模塊化(函數封裝,打包,每個功能獨立,可以單獨的定義一個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結構中,我們通過這種方式對發生的異步錯誤進行處理。