ES6的異步終極解決方案分享
前言
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)當(dāng)然簡單,如果是在這次請求完成后需要立即發(fā)起下一次請求呢?例如需要請求request10次,必須在上次請求完成后才能進(jìn)行下一次請求,來看看 回調(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()
代碼是不是更加簡短了?而且看起來和同步一樣,事實(shí)上,這就是使用同步的方式寫異步代碼,這代碼也是同步執(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種方式都詳細(xì)了解下
一.Promise
關(guān)于Promise的一些原型,函數(shù),請移步 官方鏈接
Promise的中文名,也就是承諾,保證,
大家可以將Promise理解為JS的一個承諾,也就是對異步操作的一個承諾,咱先不管異步操作是否能夠執(zhí)行成功,使用Promise的所有異步操作,JS已經(jīng)承諾處理了,咱就通過Promise的狀態(tài)來知曉異步操作的執(zhí)行結(jié)果。
一個 Promise有以下幾種狀態(tài):
- pending: 初始狀態(tài),既不是成功,也不是失敗狀態(tài)。
- fulfilled: 表示著操作完成,狀態(tài)成功。
- rejected: 意味著操作失敗。
pending 狀態(tài)的 Promise 對象可能會變?yōu)閒ulfilled 狀態(tài)并傳遞一個值給相應(yīng)的狀態(tài)處理方法,也可能變?yōu)槭顟B(tài)(rejected)并傳遞失敗信息。當(dāng)其中任一種情況出現(xiàn)時,Promise 對象的 then 方法綁定的處理方法(handlers )就會被調(diào)用
上文提到Promise的原型中的函數(shù)then,then可以接收2個參數(shù)(resolve [,reject])
第一個參數(shù)resolve 是對成功的一個處理,類型為Function。你可以在其中做 一些異步成功后的操作
第二個參數(shù)reject是對失敗的一個處理,類型為Function。你可以在其中做 一些異步失敗后的操作
一般情況下then我們只會傳一個參數(shù),也就是默認(rèn)的成功處理,失敗處理會使用 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的各大插件中也使用的相當(dāng)廣泛,所以掌握 Promise是非常有必要的
二.async / await
想詳細(xì)了解更多,請移步官方文檔
async關(guān)鍵字用來定義一個function,用來標(biāo)識此函數(shù)是一個異步函數(shù)
切記 切記 切記, await 關(guān)鍵字僅僅在 async function中有效。如果在 async function函數(shù)體外使用 await ,你只會得到一個語法錯誤SyntaxError
async關(guān)鍵字放在函數(shù)的聲明之前,例如:
async function test() { } async () => { } async data => { } class Test { async show() { } }
無論是普通的函數(shù),Lambda表達(dá)式,或是ES6的class,該關(guān)鍵字都是放置在函數(shù)的聲明之前
在調(diào)用聲明了async的函數(shù)時,會返回一個Promise對象
而await關(guān)鍵字則是放置在異步操作的調(diào)用之前,await會使得async函數(shù)在執(zhí)行到異步操作時暫停代碼執(zhí)行,直到異步操作返回的Promise狀態(tài)更改為 fulfilled 或 rejected,此時代碼會繼續(xù)執(zhí)行,并自動解析Promise返回的值,無論是成功還是失敗,都會解析到await關(guān)鍵字前面定義接收的參數(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()
上面的實(shí)例調(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標(biāo)準(zhǔn)引入的新的數(shù)據(jù)類型,使用方式是在函數(shù)名前加上*,generator和普通的函數(shù)差不多,但是可以返回多次。
嗯,所有的函數(shù)都有默認(rèn)的返回值,如果沒有明確定義,那就會返回undefined,generator也不例外!
generator使用yield關(guān)鍵字來中途返回值,請看案例
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關(guān)鍵字前面接收的值,此值是下面result.next(2)傳入的 ,next函數(shù)傳入的參數(shù),會賦值到相應(yīng)的yield關(guān)鍵字左邊接收的那個變量,在上方案例,也就是sum變量
第三次輸出的是a函數(shù)中第二個yield返回的值,此時sum為第一次next(2)傳入的2,所以此次返回的值是2+2,所以值也就是 4
第四次輸出的是最后一個next(),但是上方generator并沒有相應(yīng)的yield返回,所以此時的value為undefined
yield返回的值是一個對象,其中有done和value兩個屬性,
- done 表示該generator是否執(zhí)行完畢,當(dāng)沒有yield返回時,done的值為true,也就是代表當(dāng)前generator執(zhí)行完畢
- value表示此次yield關(guān)鍵字右方表達(dá)式返回的值,當(dāng)沒有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); }
事實(shí)證明generator是實(shí)現(xiàn)了迭代器的接口的!
嗯,關(guān)于generator的實(shí)際應(yīng)用場景,我是沒有遇見的,不過聽說 async/await是generator的語法糖??
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
- 詳解ES6之a(chǎn)sync+await 同步/異步方案
- 微信小程序 es6-promise.js封裝請求與處理異步進(jìn)程
- ES6記錄異步函數(shù)的執(zhí)行時間詳解
- ES6 javascript的異步操作實(shí)例詳解
- 詳解ES6中的三種異步解決方案
- ES6中Generator與異步操作實(shí)例分析
- ES6的Fetch異步請求的實(shí)現(xiàn)方法
- JS基于ES6新特性async await進(jìn)行異步處理操作示例
- 詳解ES6 系列之異步處理實(shí)戰(zhàn)
- ES6中的迭代器、Generator函數(shù)及Generator函數(shù)的異步操作方法
相關(guān)文章
JS使用正則表達(dá)式獲取小括號、中括號及花括號內(nèi)容的方法示例
這篇文章主要介紹了JS使用正則表達(dá)式獲取小括號、中括號及花括號內(nèi)容的方法,涉及javascript針對三種括號正則匹配的相關(guān)操作技巧,需要的朋友可以參考下2018-06-06JavaScript?顯示一個倒計時廣告牌的實(shí)現(xiàn)示例
本文主要介紹了JavaScript?顯示一個倒計時廣告牌的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04JavaScript動態(tài)添加css樣式和script標(biāo)簽
這篇文章主要介紹了JavaScript動態(tài)添加css樣式和script標(biāo)簽的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-07-07教你巧用?import.meta?實(shí)現(xiàn)熱更新的問題
import.meta?是一個給?JavaScript?模塊暴露特定上下文的元數(shù)據(jù)屬性的對象,它包含了這個模塊的信息,這篇文章主要介紹了巧用?import.meta?實(shí)現(xiàn)熱更新的問題,需要的朋友可以參考下2022-04-04JavaScript中實(shí)現(xiàn)單體模式分享
這篇文章主要介紹了JavaScript中實(shí)現(xiàn)單體模式分享,單體模式的定義:單體是一個用來劃分命名空間并將一批相關(guān)方法和屬性組織在一起的對象,如果它能夠被實(shí)例化,那么只能被實(shí)例化一次,需要的朋友可以參考下2015-01-01基于JavaScript構(gòu)建一個動態(tài)博客應(yīng)用
這篇文章主要為大家詳細(xì)介紹了如何基于JavaScript構(gòu)建一個動態(tài)博客應(yīng)用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02javascript實(shí)現(xiàn)了照片拖拽點(diǎn)擊置頂?shù)恼掌瑝Υa
這篇文章主要介紹了javascript實(shí)現(xiàn)了照片拖拽點(diǎn)擊置頂?shù)恼掌瑝Υa,效果非常不錯,這里推薦給大家,有需要的小伙伴可以參考下。2015-04-04