a();
b();
c();
複製代碼
上面代碼中,a、b、c是三個不一樣的函數,每一個函數都是一個不相關的任務。在同步模式會先執行 a 任務,再執行 b 任務,最後執行 c 任務。當b任務是一個耗時很長的請求時,而c任務是展示新頁面時,就會致使網頁卡頓。javascript
a();
//當即發送請求
ajax('url',(b)=>{
//請求回來執行
});
c();
複製代碼
上面代碼中,就是將b任務分紅了兩部分。一部分當即執行,另外一部分再請求回來後執行。也就解決了上面的問題。java
總結: 同步就是你們排隊工做,異步就是你們同時工做。node
CallBack,即回調函數,回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。即異步操做執行完成後觸發執行的函數。jquery
//當請求完成時就會觸發回調函數
$.get('url',callback);
複製代碼
回調能夠完成異步操做,但用過 jquery 的 PE 都對下面的代碼折磨過。ajax
$.ajax({
url:"type",
data:1,
success:function(a){
url:"list",
data:a,
success:function(b){
$.ajax({
url:"content",
data:b,
success:function(c){
console.log(c);
}
})
}
}
})
複製代碼
上面代碼就是傳說中的回調金字塔,又叫回調地獄。這裏還只是單純的嵌套代碼,如若再加上業務代碼,代碼可讀性可想而知。本身開發起來還好,若是這是別人的代碼,你要改其中一部分足以讓人瘋掉。promise
咱們想讀取兩個文件時,但願這兩個文件都被讀取完後,拿到結果。咱們能夠經過 node 中的 EventEmitter 類來實現,它有兩個核心方法,一個是 on(表示註冊監聽事件),一個是emit(表示發射事件)。異步
let fs=require('fs');
let EventEmitter=require('event');
let eve=new EventEmitter();
let arr=[];//存儲讀取內容
//監聽數據獲取成功事件,而後調用回調函數
eve.on('ready',function(d){
arr.push(d);
if(arr.length==2){
//兩個文件的數據
console.log(arr);
}
});
fs.readFile('./a.txt','utf8',function(err,data){
eve.emit('ready',data);
});
fs.readFile('./b.txt','utf8',function(err,data){
eve.emit('ready',data);
});
複製代碼
請求 a.txt 和 b.txt 文件數據,當成功後發佈ready事件。on 訂閱了 ready 事件,當監聽到觸發的時候,on 方法執行。async
let fs=require('fs');
function after(times,callback){
let arr=[];
return function(d){
arr.push(d);
if(arr.length==times){
callback(arr);
}
}
}
//2是一個哨兵變量,將讀取文件數據成功後執行的方法做爲回調函數傳給after方法
let out=after(2,function(data){
console.log(data);
})
fs.readFile('./a.txt','utf8',function(err,data){
out(data);
});
fs.readFile('./b.txt','utf8',function(err,data){
out(data);
});
複製代碼
上面代碼 after 方法執行時傳入的 2 至關於一個哨兵變量,須要讀取幾個文件的數據就傳幾。將須要讀取的文件數量,和讀取所有文件成功後的方法做爲回調函數傳入 after。out 爲 after 執行後返回的函數,每次獲取文件成功後執行 out 方法能夠後去到最終所有文件的數據。函數
不使用回調地獄遇到的問題是:不知道讀取文件的函數何時執行完。只有當所有讀取完成後才能執行須要文件數據的方法。ui
Generator 函數要用*號來標識,yield 關鍵字表示暫停執行的標記。Generator 函數是一個狀態機,封裝了多個內部狀態。調用一次 next 就會繼續向下執行,返回的結果是一個迭代器,因此 Generator 函數仍是一個遍歷器對象生成函數。
function* read(){
let a=yield '123';
console.log(a);
let b=yield 4;
console.log(b);
}
let it = read();
console.log(it.next('321')); // {value:'123',done:false}
console.log(it.next(1)); // 1 {value:4,done:false}
console.log(it.next(2)); // 2 {value:2,done:true}
console.log(it.next(3)); // {value:undefined,done:true}
複製代碼
上面代碼能夠看出,調用 Generator 函數後,返回的不是函數運行的結果,而是一個指向內部狀態的指針對象,也就是遍歷器對象。必須調用遍歷器對象的 next 方法,讓指針移動的下一個狀態。內部指針就會從函數開始或上次定下來的地方開始執行,直到遇到下一個 yield 語句或 return 語句爲止。value 屬性表示當前的內部狀態值,是 yield 語句後面那個表達值;done 屬性是一個布爾值,表示是否遍歷結束。
在 JavaScript 的異步發展史中,出現了一系列解決 callback 弊端的庫,而 promise 成爲了勝者,併成功地被加入了ES6標準。Promise 函數接受一個函數做爲參數,該函數有兩個參數 resolve 和 reject。promise 就像一箇中介,而它只返回可信的信息給 callback,因此 callback 必定會被異步調用,且只會被調用一次。
let p=new Promise((resolve,reject)=>{
//to do...
if(/*異步操做成功*/){
resolve(value);
}else{
reject(err);
}
});
p.then((value)=>{
//success
},(err)=>{
//failure
})
複製代碼
這樣 Promise 就解決了回調地獄的問題,好比咱們連續讀取多個文件時,寫法以下:
let fs=require('fs');
function read(url){
return new Promise((resolve,reject)=>{
fs.readFile(url,'utf8',function(err,data){
if(err) reject(err);
resolve(data);
})
}
}
read('a.txt').then((data)=>{
console.log(data);
}).then(()=>{
return read('b.txt');
}).then((data)=>{
console.log(data);
}).catch((err)=>{
console.log(err);
})
複製代碼
如此不斷的返回一個新的 Promise,這種不斷的鏈式調用,就擺脫了callback回調地獄的問題和異步代碼非線性執行的問題。
Promise 還解決了 callback 只能捕獲當前錯誤異常。Promise 和 callback 不一樣,Promise 代理着全部的 callback 的報錯,能夠由 Promise 統一處理。因此,能夠經過catch來捕獲以前未捕獲的異常。
Promise解決了callback的回調地獄問題,但Promise並無擺脫callback。因此,有沒有更好的寫法呢?
async函數是ES7中的一個新特性,它結合了Promise,讓咱們擺脫callback的束縛,直接用類同步的線性方式寫異步函數,使得異步操做變得更加方便。
let fs=require('fs');
function read(url){
return new Promise((resolve,reject)=>{
fs.readFile(url,'utf8',function(err,data){
if(err) reject(err);
resolve(data);
})
}
}
async function r(){
let a=await read('a.txt');
let b=await read('b.txt');
return a+b;
}
r().then((data)=>{
console.log(data);
});
複製代碼
至此,異步的 await 函數已經可讓咱們滿意。目前使用 Babel 已經支持 ES7 異步函數的轉碼了,你們能夠在本身的項目中開始嘗試。之後會不會出現更優秀的方案?以咱們廣大程序羣體的創造力,相信必定會有的。