隨着js的發展,在解決回調地獄問題的方案上,解決方案逐漸更新,有promise、generator和現在的async都是比較常見的;
1.Promise
這個解決方案就是把異步用同步的方式寫出來,一步一步的.then()
方法,前一個.then()執行完之後,繼續執行下一個.then()
;
function timeout (time) {
return new Promise((res, rej)=>{
setTimeout(()=>{
res('this is a returnDate');
},time)
})
};
time(5000)
.then((data)=>{console.log(data)})
.then(()=>{console.log('end')})
...
這就是promise的異步解決方案看起來還是很簡單的,其實只是以你爲邏輯簡單;
2.Generator
Generator函數是個異步管理器,這裏要提到一個協程的概念,先不用管協程是什麼鬼,先來介紹一下yield
命令,它是Generator函數中的分水嶺,在函數內部起執行權轉義的功能,什麼是執行權?好吧,這就是在Generator函數內部遇到yield命令,那麼就不往下執行了,就把執行權交出來給別的函數,等別的函數執行之後返回結果了,在返還執行權,繼續向下執行Generator函數的語句;
function generator () {
// 代碼A;
var f = yield readFile(fileA);
// 代碼A;
}
以上代碼中,當函數執行的時候分一下步驟:
a.開始執行代碼A;
b.代碼A執行到一半,遇到yield命令,暫停執行,開始執行,執行權交給yield
命令後面的函數;
c.當yield函數執行完之後,交還執行權;
d.繼續執行yield
命令下面的代碼A;
在這個過程中,代碼段A被分成兩段執行,代碼段A叫做協程A,readFile()
函數叫做協程B,協程A是異步的;
再來舉個例子:
function* gen(x){
var y = yield x+2;
return y
}
var g = gen(1);
g.next() // { value:3, done:false }
g.next() // { value:undefined, done:ture }
a.調用Generator函數並不會像其他普通函數一樣返回函數值,它會返回一個內部指針——g;
b.調用內部指針中的next
方法,會移動指針分配執行權去手動執行函數;返回結果會是一個對象,value
字段對應運算值,done
字段是布爾類型,說明函數是否執行完;
c.上面代碼中,第一次調用next
方法,函數執行到x+2
,值返回給value屬性;第二次調用next
方法,執行代碼是return y
,這時y並沒有值,只是定義了,所以第二次vlaue的值是undefined
;
d.當然next方法也是可以傳值的,如下:
var g = gen(1);
g.next() // { value:3, done:false }
g.next(2) // { value:2, done:ture }
這時第二個next
方法帶有參數2,這個參數會傳到Generator函數內部,作爲上個協程任務的返回結果被函數體內的變量y接收,因此value的值是2;
對了這裏在補一句,從上可以看出Generator函數是需要手動執行的,那麼有沒有可以讓其自執行的辦法呢,有~
co模塊小工具可以讓Generator函數自執行,只要把其當參數傳進co中;
var co = require('co');
co(gen);
3.async函數
這個es7草案,說實話也是最簡單的異步解決方案,問其原理,其實是Generator函數的語法糖;
a:
function timeout (time) {
return new Promise((res, rej)=>{
setTimeout(()=>{
res();
},time)
})
}
let start = async function () {
console.log('start');
let data = await timeout(5000);
console.log('end');
}
執行結果是:
先打印出start
,然後轉爲timeout
函數執行,執行時間爲5s,之後返回打印出end
;
是不是跟Generator函數很相似,await
跟yield
命令作用一樣~
b.當然了,也是可以獲取到返回值的:
function timeout (time) {
return new Promise((res, rej)=>{
setTimeout(()=>{
res('this is a returnDate');
},time)
})
}
let start = async function () {
console.log('start');
let data = await timeout(5000);
console.log(data);
}
這樣,5s後打印出來的就是this is a returnDate
;
c.當然,也可以捕獲錯誤:
function timeout (time) {
return new Promise((res, rej)=>{
setTimeout(()=>{
rej('this is an err');
},time)
})
}
let start = async function () {
console.log('start');
try {
let data = await timeout(5000);
console.log(data);
} catch (err) {
console.log(err);
}
}
這裏timeout
函數返回一個錯誤,那麼被try...catch...
語句中的catch
捕獲,則不執行try
中的代碼,直接執行catch
中的代碼,打印出錯誤;
d.當然了,這裏也是可以循環多執行await的
function timeout (time) {
return new Promise((res, rej)=>{
setTimeout(()=>{
rej('this is an err');
},time)
})
}
let start = async function () {
for(let i=0; i<=10; i++) {
console.log(`這是第${i}次等待`);
await timeout(5000);
}
}
這裏指出非常重要的一點,await命令必須要在async函數的上下文中,也就是說當await命令所在的環境中this的指向不是async的時候,會報錯!
所以這裏要用for循環,for…of循環,不能用map,forEach方法等;