前言
Promise
async
generator
是ES6之后才被提出來的需五,他們都能夠用來解決以前JS異步調(diào)用產(chǎn)生的一系列問題煮剧,例如大名鼎鼎的回調(diào)地獄!!!
什么是回調(diào)地獄?
在以前js中评肆,我們是無法知曉一個異步操作是否執(zhí)行完成恶复,為了在異步操作完成后執(zhí)行特定的代碼嘱腥,我們需要傳入回調(diào)函數(shù)畏鼓,請看下面的栗子:
這是一個簡單的例子,在請求完成后(可以理解為異步操作)執(zhí)行特定的代碼
//我們需要在請求完成后輸出請求完成诈豌,請看回調(diào)法
function show(params) {
request('這是請求參數(shù)', () => {
console.log('請求完成')
})
}
/**
* 模擬發(fā)起一個http請求
* @param {object} data 請求的參數(shù)
* @param {function} callBack 回調(diào)函數(shù)
*/
function request(data, callBack) {
//下面的定時器模擬請求時間
setTimeout(data => {
callBack(data);
}, 3000);
}
show()
一次回調(diào)當然簡單仆救,如果是在這次請求完成后需要立即發(fā)起下一次請求呢?例如需要請求request
10次矫渔,必須在上次請求完成后才能進行下一次請求彤蔽,來看看 回調(diào)地獄 是怎么樣的
//我們需要在請求完成后輸出請求完成,請看回調(diào)法
function show(params) {
request('這是請求參數(shù)', () => {
console.log('請求完成1次')
request('這是請求參數(shù)', () => {
console.log('請求完成2次')
request('這是請求參數(shù)', () => {
console.log('請求完成3次')
request('這是請求參數(shù)', () => {
console.log('請求完成4次')
request('這是請求參數(shù)', () => {
console.log('請求完成5次')
//這才第五次.....
})
})
})
})
})
}
/**
* 模擬發(fā)起一個http請求
* @param {object} data 請求的參數(shù)
* @param {function} callBack 回調(diào)函數(shù)
*/
function request(data, callBack) {
//下面的定時器模擬請求時間
setTimeout(data => {
callBack(data);
},1000);
}
show()
這才第5次回調(diào)庙洼,但是代碼的可讀性已經(jīng)極差了顿痪!
讓我們先看看 Promise async generator怎么解決這個問題镊辕,后面再說其使用方式
首先 Promise 篇
//我們需要在請求完成后輸出請求完成,請看回調(diào)法
function show(params) {
request('這是請求參數(shù)').then(
resolve => {
console.log('請求完成1次');
return request('這是請求參數(shù)')
}
).then(
resolve => {
console.log('請求完成2次');
return request('這是請求參數(shù)')
}
).then(
resolve => {
console.log('請求完成3次');
return request('這是請求參數(shù)')
}
).then(
resolve => {
console.log('請求完成4次');
return request('這是請求參數(shù)')
}
).then(
resolve => {
console.log('請求完成5次');
return request('這是請求參數(shù)')
}
)
}
/**
* 模擬發(fā)起一個http請求
* @param {object} data 請求的參數(shù)
* @param {function} callBack 回調(diào)函數(shù)
*/
function request(data) {
return new Promise(
resolve => {
//下面的定時器模擬請求時間
setTimeout(data => {
resolve(data)
}, 1000);
}
)
}
show()
雖然還是很長蚁袭,但是至少嵌套很少了征懈,可讀性也比之前更高
再來看看 async
切記,async
必須和Promise
配合使用
//我們需要在請求完成后輸出請求完成揩悄,請看回調(diào)法
async function show(params) {
let result = await request('這是請求參數(shù)')
console.log('請求完成1次');
result = await request('這是請求參數(shù)')
console.log('請求完成2次');
result = await request('這是請求參數(shù)')
console.log('請求完成3次');
result = await request('這是請求參數(shù)')
console.log('請求完成4次');
result = await request('這是請求參數(shù)')
console.log('請求完成5次');
}
/**
* 模擬發(fā)起一個http請求
* @param {object} data 請求的參數(shù)
* @param {function} callBack 回調(diào)函數(shù)
*/
function request(data) {
return new Promise(
resolve => {
//下面的定時器模擬請求時間
setTimeout(data => {
resolve(data)
}, 1000);
}
)
}
show()
代碼是不是更加簡短了卖哎?而且看起來和同步一樣,事實上删性,這就是使用同步的方式寫異步代碼亏娜,這代碼也是同步執(zhí)行的
最后看看 generator
//我們需要在請求完成后輸出請求完成,請看回調(diào)法
function* show() {
let a1 = yield request('請求參數(shù)', () => {
console.log('這是第1次調(diào)用');
});
let a2 = yield request('請求參數(shù)', () => {
console.log('這是第2次調(diào)用');
});
let a3 = yield request('請求參數(shù)', () => {
console.log('這是第3次調(diào)用');
});
let a4 = yield request('請求參數(shù)', () => {
console.log('這是第4次調(diào)用');
});
let a5 = yield request('請求參數(shù)', () => {
console.log('這是第5次調(diào)用');
});
}
/**
* 模擬發(fā)起一個http請求
* @param {object} data 請求的參數(shù)
* @param {function} callBack 回調(diào)函數(shù)
*/
function request(data, callBack) {
//下面的定時器模擬請求時間
setTimeout(() => {
callBack(data)
}, 1000);
}
let a = show()
a.next();
a.next();
a.next();
a.next();
a.next();
以上是異步編程的ES6解決方案镇匀,接下來讓我們把這3種方式都詳細了解下
一.Promise
關于Promise
的一些原型照藻,函數(shù),請移步 官方鏈接
Promise
的中文名汗侵,也就是承諾幸缕,保證,
大家可以將Promise
理解為JS的一個承諾晰韵,也就是對異步操作的一個承諾发乔,咱先不管異步操作是否能夠執(zhí)行成功,使用Promise
的所有異步操作雪猪,JS已經(jīng)承諾處理了栏尚,咱就通過Promise
的狀態(tài)來知曉異步操作的執(zhí)行結果。
一個 Promise
有以下幾種狀態(tài):
- pending: 初始狀態(tài)只恨,既不是成功译仗,也不是失敗狀態(tài)。
- fulfilled: 表示著操作完成官觅,狀態(tài)成功纵菌。
- rejected: 意味著操作失敗。
pending 狀態(tài)的 Promise 對象可能會變?yōu)閒ulfilled 狀態(tài)并傳遞一個值給相應的狀態(tài)處理方法休涤,也可能變?yōu)槭顟B(tài)(rejected)并傳遞失敗信息咱圆。當其中任一種情況出現(xiàn)時,Promise 對象的 then 方法綁定的處理方法(handlers )就會被調(diào)用
上文提到Promise
的原型中的函數(shù)then
功氨,then
可以接收2個參數(shù)(resolve [,reject])
第一個參數(shù)resolve
是對成功的一個處理序苏,類型為Function
。你可以在其中做 一些異步成功后的操作
第二個參數(shù)reject
是對失敗的一個處理捷凄,類型為Function
忱详。你可以在其中做 一些異步失敗后的操作
一般情況下then
我們只會傳一個參數(shù),也就是默認的成功處理跺涤,失敗處理會使用catch
函數(shù)
catch
函數(shù)只有一個參數(shù),也就是代表失敗的reject
來看看then
catch
的使用案例
function show(params) {
//正常的請求成功操作
request('這是請求參數(shù)').then(
resolve => {
console.log(resolve);
}
)
//在then里面同時做成功和失敗的操作
request('這是請求參數(shù)').then(
resolve => {
//這兒是成功
console.log(resolve);
},
reject => {
//這兒是失敗
console.log(reject);
}
)
//正常的請求失敗操作
request('這是請求參數(shù)').catch(
reject => {
console.log(reject);
}
)
}
/**
* 模擬發(fā)起一個http請求
* @param {object} data 請求的參數(shù)
* @param {function} callBack 回調(diào)函數(shù)
*/
function request(data) {
return new Promise(
(resolve, reject) => {
//下面的定時器模擬請求時間
setTimeout(data => {
// resolve('請求成功')
reject('請求失敗')
}, 1000);
}
)
}
show()
使用 Promise 能夠使你的異步操作變得更加優(yōu)雅匈睁,可讀性也比較高管钳,同時,Promise在ES6的各大插件中也使用的相當廣泛软舌,所以掌握 Promise是非常有必要的
二.async / await
想詳細了解更多,請移步官方文檔
async
關鍵字用來定義一個function
牛曹,用來標識此函數(shù)是一個異步函數(shù)
切記 切記 切記佛点, await
關鍵字僅僅在 async
function
中有效。如果在 async function
函數(shù)體外使用 await
黎比,你只會得到一個語法錯誤SyntaxError
async
關鍵字放在函數(shù)的聲明之前超营,例如:
async function test() {
}
async () => {
}
async data => {
}
class Test {
async show() {
}
}
無論是普通的函數(shù),Lambda表達式阅虫,或是ES6的class
演闭,該關鍵字都是放置在函數(shù)的聲明之前
在調(diào)用聲明了async
的函數(shù)時,會返回一個Promise
對象
而await
關鍵字則是放置在異步操作的調(diào)用之前颓帝,await
會使得async
函數(shù)在執(zhí)行到異步操作時暫停代碼執(zhí)行米碰,直到異步操作返回的Promise
狀態(tài)更改為 fulfilled 或 rejected,此時代碼會繼續(xù)執(zhí)行,并自動解析Promise
返回的值购城,無論是成功還是失敗吕座,都會解析到await
關鍵字前面定義接收的參數(shù),請看例子:
class Test {
/**
* 線程休眠
* @param {number} timer 休眠毫秒數(shù)
*/
Sleep(timer) {
return new Promise(
resolve => {
setTimeout(() => {
resolve(timer)
}, timer);
}
)
}
}
let T = new Test();
async function show() {
console.log('第一次');
await T.Sleep(1000)
console.log('第二次');
}
show()
上面的實例調(diào)用show
函數(shù)瘪板,會立即打印出 第一次,延時1000
毫秒后吴趴,會打印出 第二次
原理嘛,就是模仿Java的線程休眠函數(shù)Sleep
在打印了 第一次 后侮攀,會調(diào)用T的Sleep
函數(shù)锣枝,Sleep
函數(shù)返回了一個Promise
,在定時器執(zhí)行完畢后調(diào)用Promise
的resolve
函數(shù),會將Promise
的狀態(tài)更改為 fulfilled兰英,此時await
檢測到Promise
的狀態(tài)更改撇叁,繼續(xù)執(zhí)行代碼,輸出 第二次
三 . generator
generator
(生成器)是ES6標準引入的新的數(shù)據(jù)類型,使用方式是在函數(shù)名前加上*
,generator
和普通的函數(shù)差不多箭昵,但是可以返回多次税朴。
嗯,所有的函數(shù)都有默認的返回值家制,如果沒有明確定義正林,那就會返回undefined
,generator
也不例外!
generator
使用yield
關鍵字來中途返回值颤殴,請看案例
function* a(num) {
let sum = yield num + 1
console.log(sum);//2 此處是next(r.value)傳入的值
let sum2 = yield sum + 2
}
let result = a(1);
let r = result.next()
console.log(r);//此處返回第一次yield的值 2
console.log(result.next(2));//此處返回第二次yield的值 4
console.log(result.next());//此處并沒有第三次yield
第一次輸出的是第一次
yield
的值觅廓,此時num
為調(diào)用a
函數(shù)時傳入的值 1
,yield
返回了num+1
,所以第一行打印的對象 value
值是 2第二次打印的是
sum
值涵但,也就是第一個yield
關鍵字前面接收的值杈绸,此值是下面result.next(2)
傳入的 帖蔓,next
函數(shù)傳入的參數(shù),會賦值到相應的yield
關鍵字左邊接收的那個變量瞳脓,在上方案例塑娇,也就是sum
變量第三次輸出的是
a
函數(shù)中第二個yield
返回的值,此時sum
為第一次next(2)
傳入的2,所以此次返回的值是2+2
劫侧,所以值也就是 4第四次輸出的是最后一個
next()
埋酬,但是上方generator
并沒有相應的yield
返回,所以此時的value
為undefined
yield
返回的值是一個對象烧栋,其中有done
和value
兩個屬性写妥,
-
done 表示該
generator
是否執(zhí)行完畢,當沒有yield
返回時审姓,done
的值為true
,也就是代表當前generator
執(zhí)行完畢 -
value表示此次
yield
關鍵字右方表達式返回的值,當沒有yield
時,value
為undefined
generator
是支持迭代器操作的珍特,例:
function* a(num) {
let sum = yield num + 1
console.log(sum);//2 此處是next(r.value)傳入的值
let sum2 = yield sum + 2
}
let result = a(1);
for (const key of result) {
console.log(key);
}
事實證明
generator
是實現(xiàn)了迭代器的接口的!嗯,關于
generator
的實際應用場景,我是沒有遇見的魔吐,不過聽說 async/await是generator的語法糖扎筒??