http://es6.ruanyifeng.com/阮一峯ES6
異步,簡單說就是一個任務不是連續完成的。比如,有一個任務是讀取文件進行處理,向操作系統發出請求,要求讀取文件;程序執行其他任務;等到操作系統返回文件,再處理文件。相應地,連續的執行就叫做同步,不能插入其它任務,操作系統從硬盤讀取文件的這段時間,程序只能乾等着。
異步編程的方法:回調函數、事件監聽、發佈/訂閱、Promise對象、Generator函數。
回調函數callback:把任務的第二段單獨寫在一個函數中,等到重新執行這個任務時,直接調用這個函數。
// 讀取文件進行處理
// 等到操作系統返回/etc/passwd這個文件後,回調函數纔會執行。
fs.readFile('/etc/passwd','utf-8',function(err,data){
if(err) throw err;
console.log(data);
});
// 回調 寫法複雜,難以分清執行順序,影響維護
let ajax=function(callback){
console.log("ajax");
setTimeout(function(){
callback&&callback.call()
},1000);
}
ajax(function(){
console.log('callback');
});
十三、Promise對象
Promise是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更合理和更強大。
Promise是一個容器,保存着某個未來纔會結束的事件(通常是一個異步操作)的結果。Promise是一個對象,從它可以獲取異步操作的消息,提供統一的 API,各種異步操作都可以用同樣的方法進行處理。
(1)對象的狀態不受外界影響。Promise對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。只要這兩種情況發生,狀態就凝固了,會一直保持這個結果,稱爲resolved(已定型)。如果改變已經發生了,再對Promise對象添加回調函數,也會立即得到這個結果。而對於事件(Event),如果錯過了它再去監聽,是得不到結果的。
Promise對象將異步操作以同步操作的流程表達,避免了層層嵌套的回調函數;提供統一的接口,使控制異步操作更加容易。也有一些缺點,一旦新建它就會立即執行,無法中途取消;如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部;當處於pending狀態時,無法得知目前進展到哪一個階段。
如果某些事件不斷地反覆發生,一般使用Stream模式比部署Promise更好。
1、new Promise()
Promise對象是一個構造函數,用來生成Promise實例。接受一個函數作爲參數,該函數的參數分別是resolve和reject函數。
resolve函數將Promise對象的狀態從“未完成”變爲“成功”,在異步操作成功時調用,並將異步操作的結果,作爲參數傳遞出去;reject函數將Promise對象的狀態從“未完成”變爲“失敗”,在異步操作失敗時調用,並將異步操作報出的錯誤,作爲參數傳遞出去。
Promise新建後就會立即執行。調用resolve或reject以後,Promise的使命就完成了,後繼操作應該放到then方法裏面,而不應該直接寫在resolve或reject後面。最好在它們前面加上return語句。
const promise=new Promise(function(resolve,reject){
// ... some code
if(/* 異步操作成功 */){
resolve(value);
}else{
reject(error);
}
});
2、Promise.prototype.then()、catch()和finally()
Promise實例生成後,用then方法分別指定resolve和rejected狀態的回調函數(異步操作無法使用return把結果返回給調用者),第二個函數是可選的。
返回新的Promise實例,可以採用鏈式寫法,指定一組按照次序調用的回調函數。前一個回調函數,有可能返回的還是一個Promise對象(即有異步操作),後一個回調函數,會等待該Promise對象的狀態發生變化,纔會被調用。
promise.then(function(value){
// success
},function(error){
// failure
});
/** 執行步驟:(假設有一個.then)
1. 定義函數ajax;
2. 執行ajax();
3. 打印"ajax",new Promise()並返回;
function(resolve,reject)開始異步執行;
4. .then指定了function(resolve,reject)的回調;
5. function(resolve,reject)執行完。
*/
let ajax=function(){
console.log("ajax");
return new Promise(function(resolve,reject){
setTimeout(resolve,1000);
})
}
ajax()
.then(function(){
console.log("timeout1");
return new Promise(function(resolve,reject){
setTimeout(resolve,2000);
})
})
.then(function(){
console.log("timeout2")
})
catch方法是.then(null,rehection)或.then(undefined,rejection)的別名,用於指定發生錯誤時(rejected狀態)的回調函數。一般不要在then方法使用第二個參數),總是使用catch方法。Promise對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲爲止,即錯誤總是會被下一個catch語句捕獲。
Promise對象拋出的錯誤不會傳遞到外層代碼,不會終止程序。
const promise=new Promise(function(resolve,reject){
throw new Error('test');
});
promise.catch(function(error){
console.log(error);
})
let ajax=function(){
return new Promise(function(resolve,reject){
throw new Error("promise1 error");
return resolve();
})
}
/* 錯誤一直向後傳遞,直到catch捕獲,跳過中間的.then */
ajax()
.then(function(){
console.log("promise1");
return new Promise(function(resolve,reject){
return resolve();
})
})
.then(function(){
console.log("promise2");
})
.catch(function(){
console.log("reject");
})
console.log("outside");
// outside
// reject
/* 每一步.then通過reject處理錯誤,不影響之後的.then */
ajax()
.then(function(){
console.log("promise1");
return new Promise(function(resolve,reject){
return resolve();
})
},function(error){
console.log("reject"+error);
})
.then(function(){
console.log("promise2");
})
console.log("outside");
// outside
// rejectError: promise1 error
// promise2
finally方法指定不管Promise對象最後狀態如何,都會執行的操作。
server.listen(port)
.then(function(){
// ...
})
.finally(server.stop);
3、Promise.all()和race()
Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。
參數必須具有Iterator接口,且返回的每個成員都是Promise實例,否則調用Promise.resolve方法將成員轉爲Promise實例。
只有所有成員的狀態是fulfilled,p的狀態纔是fulfilled,此時將所有成員的返回值組成一個數組傳給p的回調函數。如果p的狀態是rejected,將第一個被reject的實例的返回值傳給p的回調函數。
const p=Promise.all([p1,p2,p3]);
/* 所有圖片加載完再添加到頁面 */
function loadImg(src){
return new Promise((resolve,reject)=>{
let img=document.createElement('img');
img.src=src;
img.οnlοad=function(){
resolve(img);
}
img.οnerrοr=function(err){
reject(err);
}
})
}
function showImgs(imgs){
imgs.forEach(function(img){
document.body.appendChild(img);
})
}
Promise.all([
loadImg("https://img-blog.csdn.net/20180830223234851?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhb2NvbmcxOTkz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70"),
loadImg("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1546709317586&di=b8a1d3ca2d74a9a0e143a08487a753b5&imgtype=0&src=http%3A%2F%2Fdesk-fd.zol-img.com.cn%2Ft_s960x600c5%2Fg5%2FM00%2F02%2F03%2FChMkJlbKxpKIBV3oABpO1k7kXwEAALHnQB8IREAGk7u679.jpg"),
loadImg("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1546709346183&di=714f1f97a96934a9a3ee1d42a5fbd775&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201405%2F22%2F20140522162020_jJPVm.jpeg")
]).then(showImgs)
Promise.race方法與Promise.all方法的不同之處在於,只要成員中有一個實例率先改變狀態,p的狀態就跟着改變。率先改變的Promise實例的返回值,就傳遞給p的回調函數。
/* 有一張圖片加載完就添加到頁面 */
function showImg(img){
document.body.appendChild(img);
}
Promise.race([
loadImg("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1546709317586&di=b8a1d3ca2d74a9a0e143a08487a753b5&imgtype=0&src=http%3A%2F%2Fdesk-fd.zol-img.com.cn%2Ft_s960x600c5%2Fg5%2FM00%2F02%2F03%2FChMkJlbKxpKIBV3oABpO1k7kXwEAALHnQB8IREAGk7u679.jpg"),
loadImg("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1546709346183&di=714f1f97a96934a9a3ee1d42a5fbd775&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201405%2F22%2F20140522162020_jJPVm.jpeg"),
loadImg("https://img-blog.csdn.net/20180830223234851?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhb2NvbmcxOTkz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70")
]).then(showImg)
4、Promise.resolve()和reject()
Promise.resolve方法將現有對象轉爲Promise對象。
Promise.reject(reason)方法返回一個新的Promise實例,該實例的狀態爲rejected。參數會原封不動地作爲reject的理由,變成後續方法的參數。
十四、Generator函數
1、簡介
語法上,Generator函數是一個狀態機,封裝了多個內部狀態;返回一個遍歷器對象,可以依次遍歷Generator函數內部的每一個狀態。形式上,Generator函數是一個普通函數,但function關鍵字與函數名之間有一個星號,函數體內部使用yield表達式定義不同的內部狀態。
function* helloWorldGenerator(){
yield 'hello';
yield 'world';
return 'ending';
}
let hw=helloWorldGenerator();
console.log(hw.next()); // {value: "hello", done: false}
console.log(hw.next()); // {value: "world", done: false}
console.log(hw.next()); // {value: "ending", done: true}
console.log(hw.next()); // {value: undefined, done: true}
上述函數有三個狀態hello,world和return語句(結束執行)。調用Generator函數後,該函數並不執行,返回一個指向內部狀態的指針對象(遍歷器對象)。每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)爲止。將yield或return後面表達式的值作爲返回的對象的value值。
Generator函數是分段執行的,yield表達式是暫停執行的標記,next方法可以恢復執行。可以不用yield表達式,這時就變成了一個單純的暫緩執行函數。
2、狀態機
let clock=function* (){
while(true){
console.log("Tick!");
yield;
console.log("Tock!");
yield;
}
}
let status=clock();
status.next(); // Tick!
status.next(); // Tock!
status.next(); // Tick!
status.next(); // Tock!
十五、async函數
async函數就是將Generator函數的星號(*)替換成async,將yield替換成await。對Generator函數的改進,體現在以下幾點。
(1)內置執行器:才能真正執行,而async函數自帶執行器,執行與普通函數相同,不需要調用next方法或用co模塊。
(2)更好的語義:對比星號和yield,async表示函數裏有異步操作,await表示緊跟在後面的表達式需要等待結果。
(3)更廣的適用性:async函數的await命令後面,可以是Promise對象和原始類型的值(自動轉成立即resolved的Promise對象)。
(4)返回值是Promise。
async函數可以看作多個異步操作,包裝成的一個Promise對象,而await命令是內部then命令的語法糖。