await-to-js源碼深入理解處理異步任務(wù)用法示例
如何處理異步任務(wù)?
我們先從一個(gè)老生常談的問題開始。
回調(diào)函數(shù)
由于javascript
是一門單線程的語言,所以我們?cè)缙趤硖幚懋惒綀?chǎng)景的時(shí)候,大部分是通過回調(diào)函數(shù)來進(jìn)行處理的。
var fn = function(callback){ setTimeout(function(){ callback() },1000) } fn(function(){console.log('hello, pino')})
例如上面這個(gè)例子,fn函數(shù)是一個(gè)異步函數(shù),里面執(zhí)行的setTimeout
將會(huì)在1s之后調(diào)用傳入的callback
函數(shù),打印出hello,pino
這個(gè)結(jié)果。
但是當(dāng)我們有多個(gè)異步操作的時(shí)候,就需要有多個(gè)異步函數(shù)進(jìn)行嵌套,代碼將會(huì)變得更加臃腫和難以維護(hù)。
setTimeout(function(){ console.log('執(zhí)行了') setTimeout(function(){ console.log('再次執(zhí)行了') //..... },2000) },1000)
同樣的,還有一個(gè)例子: 假設(shè)我們有fn1,fn2,fn3三個(gè)異步函數(shù):
let fn1 = function(){ setTimeout(function(){ console.log('pino') },1000) } let fn2 = function(){ setTimeout(function(){ console.log('愛吃') },3000) } let fn3 = function(){ setTimeout(function(){ console.log('瓜') },2000) }
我們想順序?qū)θ齻€(gè)函數(shù)的結(jié)果進(jìn)行順序打印,那么使用傳統(tǒng)的回調(diào)函數(shù)來實(shí)現(xiàn)的話,我們可以這樣寫:
var makefn = function(text,callback,timer){ setTimeout(function(){ console.log(text) callback() },timer) } makefn('pino',function(){ makefn('愛吃',function(){ makefn('瓜',function(){ console.log('結(jié)束了~') },2000) },3000) },1000)
可以看到當(dāng)回調(diào)任務(wù)過多的時(shí)候,我們的代碼將會(huì)變的非常臃腫,尤其是多個(gè)異步函數(shù)之間層層嵌套,這就形成了回調(diào)地獄。
使用回調(diào)函數(shù)的方式來處理異步任務(wù),當(dāng)回調(diào)函數(shù)過多時(shí),對(duì)開發(fā)者的心智負(fù)擔(dān)是非常重的。
Promise
promise
對(duì)象的出現(xiàn)其實(shí)是對(duì)js處理異步任務(wù)邁出的一大步,它提供了非常多的針對(duì)異步的處理方法,錯(cuò)誤捕獲鏈?zhǔn)秸{(diào)用...
Promise
對(duì)象是一個(gè)構(gòu)造函數(shù),用來生成Promise
實(shí)例。
Promise
構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù),該函數(shù)的兩個(gè)參數(shù)分別是resolve
和reject
。
resolve
函數(shù): 將Promise
對(duì)象的狀態(tài)從“未完成”變?yōu)?ldquo;成功”(即從 pending
變?yōu)?resolved
),在異步操作成功時(shí)調(diào)用,并將異步操作的結(jié)果,作為參數(shù)傳遞出去;
reject
函數(shù): 將Promise
對(duì)象的狀態(tài)從“未完成”變?yōu)?ldquo;失敗”(即從 pending
變?yōu)?rejected
),在異步操作失敗時(shí)調(diào)用,并將異步操作報(bào)出的錯(cuò)誤,作為參數(shù)傳遞出去。
const person = new Promise((resolve,reject) => { let num = 6; if(num>5){ resolve() }else{ reject() } })
Promise
實(shí)例生成以后,可以用then
方法分別指定resolved
狀態(tài)和rejected
狀態(tài)的回調(diào)函數(shù)。
promise.then(function(value) { // success }, function(error) { // failure });
then
方法可以接受兩個(gè)回調(diào)函數(shù)作為參數(shù)。第一個(gè)回調(diào)函數(shù)是Promise
對(duì)象的狀態(tài)變?yōu)?code>resolved時(shí)調(diào)用,第二個(gè)回調(diào)函數(shù)是Promise
對(duì)象的狀態(tài)變?yōu)?code>rejected時(shí)調(diào)用。其中,第二個(gè)函數(shù)是可選的,這兩個(gè)函數(shù)都接受Promise
對(duì)象傳出的值作為參數(shù)。
例如我們將上面的順序打印三個(gè)異步函數(shù)進(jìn)行改造:
makefn('pino',function(){ makefn('愛吃',function(){ makefn('瓜',function(){ console.log('結(jié)束了~') },2000) },3000) },1000) //改造后 fn('pino',1000).then(function(){ return fn('愛吃',3000) }) .then(function(){ return fn('瓜',2000) }) .then(function(){ console.log('結(jié)束了~') })
可以看到改造完成后的代碼變得非常具有可讀性和條理性。 由于本文的主角不是Promise對(duì)象,所以想要深入了解請(qǐng)移步:es6.ruanyifeng.com/#docs/promi…
async
ES2017 標(biāo)準(zhǔn)引入了 async
函數(shù),使得異步操作變得更加方便。
async
函數(shù)返回一個(gè) Promise
對(duì)象,可以使用then
方法添加回調(diào)函數(shù)。當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到await
就會(huì)先返回,等到異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語句。
// 函數(shù)前面加入async關(guān)鍵字 async function getAllData(name) { // 遇到await會(huì)暫停,并返回值 const data = await getData(name); const options = await getSelect(name); return options; } getAllData('pino').then(function (result) { console.log(result); });
下面繼續(xù)使用async
的方式來改造一下文章開頭的例子:
async function makeFn() { let fn1 = await fn1() let fn2 = await fn2() let fn3 = await fn3() }
async
函數(shù)的出現(xiàn)幾乎將異步函數(shù)完全變?yōu)榱送降膶懛?,使異步任?wù)更容易維護(hù)。
Generator
形式上, Generator
函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征。
一是,function
關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào);
二是,函數(shù)體內(nèi)部使用yield
表達(dá)式,定義不同的內(nèi)部狀態(tài)(yield
在英語里的意思就是“產(chǎn)出”)。
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var p1 = helloWorldGenerator();
上面代碼定義了一個(gè) Generator
函數(shù)helloWorldGenerator
,它內(nèi)部有兩個(gè)yield
表達(dá)式(hello和world
)
即該函數(shù)有三個(gè)狀態(tài):hello,world
和 return
語句(結(jié)束執(zhí)行)。
然后, Generator
函數(shù)的調(diào)用方法與普通函數(shù)一樣,也是在函數(shù)名后面加上一對(duì)圓括號(hào)。
不同的是,調(diào)用 Generator
函數(shù)后,該函數(shù)并不執(zhí)行,返回的也不是函數(shù)運(yùn)行結(jié)果,而是一個(gè)指向內(nèi)部狀態(tài)的指針對(duì)象,也就是上一章介紹的遍歷器對(duì)象(Iterator Object
)。 下一步,必須調(diào)用遍歷器對(duì)象的next
方法,使得指針移向下一個(gè)狀態(tài)。
也就是說,每次調(diào)用next
方法,內(nèi)部指針就從函數(shù)頭部或上一次停下來的地方開始執(zhí)行,直到遇到下一個(gè)yield
表達(dá)式(或return
語句)為止。
換言之, Generator
函數(shù)是分段執(zhí)行的,yield
表達(dá)式是暫停執(zhí)行的標(biāo)記,而next
方法可以恢復(fù)執(zhí)行。
p1.next() // { value: 'hello', done: false } p1.next() // { value: 'world', done: false } p1.next() // { value: 'ending', done: true } p1.next() // { value: undefined, done: true }
上面代碼一共調(diào)用了四次next方法。
Generator
函數(shù)也可以進(jìn)行異步任務(wù)的處理,上面的async
函數(shù)就是Generator
函數(shù)的語法糖,而兩者之間最大的區(qū)別就是async
函數(shù)內(nèi)置了自執(zhí)行器,也就是說無需手動(dòng)調(diào)用next()
方法,async
函數(shù)就會(huì)幫我們繼續(xù)向下執(zhí)行,而Generator
函數(shù)不會(huì)自動(dòng)調(diào)用next()
方法,只能進(jìn)行手動(dòng)調(diào)用,下面實(shí)現(xiàn)一個(gè)簡(jiǎn)易執(zhí)行器:
// 接受一個(gè)Generator函數(shù) function run(gen){ var g = gen(); function next(data){ var result = g.next(data); if (result.done) return result.value; // 只要返回的dong不為true,沒有執(zhí)行完畢,就繼續(xù)調(diào)用next函數(shù),繼續(xù)執(zhí)行 result.value.then(function(data){ next(data); }); } next(); } run(gen);
使用Generator
函數(shù)來該寫一下之前的案例,其實(shí)只需要將await
更換為yield
:
function* makeFn() { let fn1 = yield fn1() let fn2 = yield fn2() let fn3 = yield fn3() }
本文只是簡(jiǎn)略的講解了Generator
函數(shù)和async
函數(shù),如果像深入學(xué)習(xí),請(qǐng)移步:
es6.ruanyifeng.com/#docs/async
es6.ruanyifeng.com/#docs/gener…
什么是await-to-js?
說了這么多,今天的主角await-to-js
到底是干啥的?解決了什么問題?
先來看一下作者的定義:
Async await wrapper for easy error handling without try-catch。
異步等待封裝器,便于錯(cuò)誤處理,不需要try-catch
。
先來看一下如何使用:
安裝
npm i await-to-js --save
對(duì)比一下使用await-to-js
后,我們?cè)诖a中處理錯(cuò)誤捕獲有什么不同,這里使用async
函數(shù)進(jìn)行處理:
// async的處理方式 function async getData() { try { const data1 = await fn1() } catch(error) { return new Error(error) } try { const data2 = await fn2() } catch(error) { return new Error(error) } try { const data3 = await fn3() } catch(error) { return new Error(error) } }
// 使用await-to-js后 import to from './to.js'; function async getData() { const [err, data1] = await to(promise) if(err) throw new (error); const [err, data2] = await to(promise) if(err) throw new (error); const [err, data3] = await to(promise) if(err) throw new (error); }
可以看到,使用await-to-js后我們的代碼變得精簡(jiǎn)了許多,在使用async
函數(shù)時(shí),需要手動(dòng)使用try...catch
來進(jìn)行錯(cuò)誤捕獲,而await-to-js
直接就可以將錯(cuò)誤返回給用戶。
所以根據(jù)上面的例子,可以得出結(jié)論,await-to-js
的作用就是封裝了錯(cuò)誤捕獲的處理函數(shù),使異步的操作更加的方便。
那么await-to-js
是如何實(shí)現(xiàn)的呢?
源碼解析
其實(shí)await-to-js
的源碼非常短,只有15行,可以直接看一下源碼中是如何實(shí)現(xiàn)的(為了查看源碼更加的直觀,下面的源碼已經(jīng)去除了typescript
語法):
export function to( promise, errorExt ){ return promise .then((data) => [null, data]) .catch((err) => { if (errorExt) { const parsedError = Object.assign({}, err, errorExt); return [parsedError, undefined]; } return [err, undefined]; }); }
可以看到await-to-js
中直接返回了to
函數(shù),他接受兩個(gè)參數(shù),promise
和errorExt
,其中promise
參數(shù)接受一個(gè)Promis對(duì)象,而errorExt
參數(shù)是可選的,先來看一下如果不傳入errorExt
參數(shù)是什么樣子的:
export function to(promise, errorExt){ // 使用then和catch來執(zhí)行和捕獲錯(cuò)誤 return promise .then((data) => [null, data]) .catch((err) => { return [err, undefined]; }); }
to
函數(shù)直接返回了傳入的Promise
對(duì)象,并定義了then
函數(shù)和catch
函數(shù),無論成功還是失敗都返回一個(gè)數(shù)組,數(shù)組的第一項(xiàng)是錯(cuò)誤結(jié)果,如果執(zhí)行成功則返回null
,執(zhí)行失敗則返回錯(cuò)誤信息,數(shù)組的第二項(xiàng)為執(zhí)行結(jié)果,執(zhí)行成功則返回響應(yīng)成功的結(jié)果,如果執(zhí)行失敗則返回undefined
,這也是非常符合預(yù)期的。
那么第二個(gè)參數(shù)是干什么的呢?第二個(gè)參數(shù)errorExt
是可選的,他接收一個(gè)對(duì)象,主要用于接收用戶自定義的錯(cuò)誤信息,然后使用Object.assign
將自定義信息與錯(cuò)誤信息合并到一個(gè)對(duì)象,返回給用戶。
.catch((err) => { if (errorExt) { // 合并錯(cuò)誤對(duì)象:默認(rèn)錯(cuò)誤信息+用戶自定義錯(cuò)誤信息 const parsedError = Object.assign({}, err, errorExt); // 返回錯(cuò)誤結(jié)果 return [parsedError, undefined]; } });
剛開始看源碼的時(shí)候各種不適應(yīng),但是只要沉下心去一步一步的調(diào)試,結(jié)合測(cè)試用例,有些東西真的沒有想象中那么難,主要還是重在行動(dòng),想到了一個(gè)念頭和想法就趕緊去做,拒絕拖沓,只有真正的行動(dòng)去學(xué)習(xí),去獲取,去感知,才能真正的進(jìn)步!??
最后 ?
未來可能會(huì)更新實(shí)現(xiàn)mini-vue3
和javascript
基礎(chǔ)知識(shí)系列,希望能一直堅(jiān)持下去,期待多多點(diǎn)贊????,一起進(jìn)步!
以上就是await-to-js源碼更完美處理異步任務(wù)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于await異步任務(wù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Umi4集成阿里低代碼框架lowcode-engine實(shí)現(xiàn)
這篇文章主要為大家介紹了Umi4集成阿里低代碼框架lowcode-engine實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08js實(shí)現(xiàn)兔年轉(zhuǎn)圈圈動(dòng)畫示例
這篇文章主要為大家介紹了js實(shí)現(xiàn)兔年轉(zhuǎn)圈圈動(dòng)畫示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01

TypeScript中Module使用區(qū)別及模塊路徑解析規(guī)則