一道簡(jiǎn)單又有意思的 JavaScript 手寫(xiě)題 — 異步加法 asyncAdd
前言
在掘金上發(fā)現(xiàn)一道既簡(jiǎn)單但個(gè)人覺(jué)得還挺有意思的一道題,題目如下:
//?異步加法
function?asyncAdd(a,b,cb){
??setTimeout(()?=>?{
????cb(null,?a?+?b)
??},?Math.random()?*?1000)
}
async?function?total(){
??const?res1?=?await?sum(1,2,3,4,5,6,4)
??const?res2?=?await?sum(1,2,3,4,5,6,4)
??return?[res1,?res2]
}
total()
//?實(shí)現(xiàn)下 sum 函數(shù)。注意不能使用加法,在 sum 中借助 asyncAdd 完成加法。盡可能的優(yōu)化這個(gè)方法的時(shí)間。
function?sum(){
}
復(fù)制代碼
你可以直接嘗試實(shí)現(xiàn)下,考察下自己的思維和 JavaScript 基礎(chǔ)知識(shí)的聯(lián)系如何,大佬請(qǐng)繞行!
估計(jì)大多數(shù)人第一眼看下都不知道這題目到底要干啥(我不說(shuō)就沒(méi)人知道我也是),但是在看第二遍的時(shí)候估計(jì)就差不多明白具體是要考察什么內(nèi)容了,下面就一起來(lái)分析分析吧?。?!
14040709.jpg
分析 asyncAdd
這里先放置最終結(jié)論:
-
只能修改
sum部分的內(nèi)容,sum可接收任意長(zhǎng)度的參數(shù) -
sum中只能通過(guò)asyncAdd實(shí)現(xiàn)加法計(jì)算 -
sum中需要處理異步邏輯,需要使用Promise -
需要優(yōu)化
sum方法的計(jì)算時(shí)間
下面是分別通過(guò)對(duì)代碼的不同部分進(jìn)行分析,獲取到的相關(guān)的信息。
直觀的基本要求
//?實(shí)現(xiàn)下 sum 函數(shù)。注意不能使用加法,在 sum 中借助 asyncAdd 完成加法。盡可能的優(yōu)化這個(gè)方法的時(shí)間。?
function?sum(){?}
復(fù)制代碼
最直觀的方式就是通過(guò)上述的文字描述部分,可以很容易知道題目具體要求:
-
實(shí)現(xiàn)
sum函數(shù),即只能修改sum部分的內(nèi)容 -
不能直接使用加法(+),通過(guò)
asyncAdd實(shí)現(xiàn)加法 -
優(yōu)化
sum方法的計(jì)算時(shí)間
隱藏的考察點(diǎn) — setTimeout & cb
//?異步加法
function?asyncAdd(a,?b,?cb){
??setTimeout(()?=>?{
????cb(null,?a?+?b)
??},?Math.random()?*?1000)
}
復(fù)制代碼
從上述內(nèi)容來(lái)看,最明顯的就是 setTimeout 和 cb 了,其實(shí)這不難理解因?yàn)樵?asyncAdd 中使用了 setTimeout 只能通過(guò)回調(diào)函數(shù) cb 將本次計(jì)算結(jié)果返回出去,那其中的第一個(gè)參數(shù) null 代表什么呢?其實(shí)可以認(rèn)為它是一個(gè)錯(cuò)誤信息對(duì)象,如果你比較了解 node 的話(huà),就會(huì)知道在 node 中的異步處理的回調(diào)函數(shù)通常第一個(gè)參數(shù)就是錯(cuò)誤對(duì)象,用于傳遞給外部在發(fā)生錯(cuò)誤時(shí)自定義后續(xù)執(zhí)行邏輯等。
一句話(huà):cb 函數(shù)會(huì)接收 錯(cuò)誤對(duì)象 和 計(jì)算結(jié)果 作為參數(shù)傳遞給外部。
隱藏的考察點(diǎn) — async & await
async?function?total(){
??const?res1?=?await?sum(1,2,3,4,5,6,4)
??const?res2?=?await?sum(1,2,3,4,5,6,4)
??return?[res1,?res2]
}
復(fù)制代碼
從上述的這部分來(lái)看,**sum** 方法的 返回值 肯定是一個(gè) promise 類(lèi)型的,因?yàn)樽钋懊婷黠@的使用了 await sum(...) 的形式。
另外 total 函數(shù)返回值也必然是一個(gè) promise 類(lèi)型,因?yàn)檎麄€(gè) total 函數(shù)被定義為了一個(gè) async 異步函數(shù),**可點(diǎn)擊此處查看詳細(xì)內(nèi)容**[2]。
一句話(huà):sum 需要返回 promise 類(lèi)型的值,即 sum 一定會(huì)使用到 promise,并且從 sum(1,2,3,4,5,6,4) 可知 sum 可接收任意長(zhǎng)度的參數(shù)。
題目的實(shí)現(xiàn)方式并不唯一,但核心點(diǎn)仍舊是考察你對(duì) JavaScript 異步操作方面的思考和實(shí)現(xiàn),以下只是給出了一種直觀的解決方式,算是 拋磚引玉,可以將你的最優(yōu)解貢獻(xiàn)在評(píng)論區(qū)!
具體實(shí)現(xiàn)
實(shí)現(xiàn)思路如下:
- 考慮到外部參數(shù)長(zhǎng)度不固定,使用剩余運(yùn)算符接收所有傳入的參數(shù)
-
考慮到
asyncAdd中的異步操作,將其封裝為Promise的實(shí)現(xiàn),即caculate函數(shù) -
考慮到
asyncAdd實(shí)際只能一次接收兩個(gè)數(shù)字進(jìn)行計(jì)算,使用循環(huán)的形式將多個(gè)參數(shù)分別傳入 -
考慮到通過(guò)循環(huán)處理異步操作的順序問(wèn)題,使用
async/await來(lái)保證正確的執(zhí)行順序,且async函數(shù)的返回值正好符合sum是Promise類(lèi)型的要求
具體代碼如下:
//?通過(guò)?ES6?的剩余運(yùn)算符(...)?接收外部傳入長(zhǎng)度不固定的參數(shù)
async?function?sum(...nums:?number[])?{
????//?封裝?Promise?
????function?caculate(num1:?number,?num2:?number)?{
????????return?new?Promise((resolve,?reject)?=>?{
????????????//?調(diào)用?asyncAdd?實(shí)現(xiàn)加法
????????????asyncAdd(num1,?num2,?(err:?any,?rs:?number)?=>?{
????????????????//?處理錯(cuò)誤邏輯
????????????????if?(err)?{
????????????????????reject(err);
????????????????????return;
????????????????}
????????????????//?向外部傳遞對(duì)應(yīng)的計(jì)算結(jié)果
????????????????resolve(rs);
????????????});
????????})
????}
????let?res:?any?=?0;
????
????//?通過(guò)遍歷將參數(shù)一個(gè)個(gè)進(jìn)行計(jì)算
????for?(const?n?of?nums)?{
????????//?為了避免異步執(zhí)行順序問(wèn)題,使用?await?等待執(zhí)行結(jié)果?
????????res?=?await?caculate(res,?n);
????}
????return?res;
}
復(fù)制代碼
進(jìn)行優(yōu)化
抽離內(nèi)層函數(shù)
-
caculate函數(shù)可抽離到sum函數(shù)外層 -
asyncAdd函數(shù)的回調(diào)函數(shù)沒(méi)必要抽離,因?yàn)樗蕾?lài)的參數(shù)和外部方法太多
function?caculate(num1:?number,?num2:?number)?{
????return?new?Promise((resolve,?reject)?=>?{
????????asyncAdd(num1,?num2,?(err:?any,?rs:?number)?=>?{
????????????if?(err)?{
????????????????reject(err);
????????????????return;
????????????}
????????????resolve(rs);
????????});
????})
}
async?function?sum(...nums:?number[])?{
????let?res:?any?=?0;
????for?(const?n?of?nums)?{
????????res?=?await?caculate(res,?n);
????}
????return?res;
}
復(fù)制代碼
緩存計(jì)算結(jié)果
其實(shí)你仔細(xì)觀察 total 方法,其中 sum 調(diào)用了兩次,而且參數(shù)還是一模一樣的,目的就是提示你在第二次計(jì)算相同內(nèi)容時(shí)結(jié)果直接 從緩存中獲取,而不是在通過(guò)異步計(jì)算。
async?function?total(){
??const?res1?=?await?sum(1,2,3,4,5,6,4)
??const?res2?=?await?sum(1,2,3,4,5,6,4)
??return?[res1,?res2]
}
復(fù)制代碼
以下只是一個(gè)簡(jiǎn)單的緩存方案的實(shí)現(xiàn),不必過(guò)于糾結(jié),具體實(shí)現(xiàn)如下:
const?cash:?any?=?{};
function?isUndefined(target:?any)?{
????return?target?===?void?0;
}
async?function?sum(...nums:?number[])?{
????let?res:?any?=?0;
????const?key?=?nums.join('+');
????if?(!isUndefined(cash[key]))?return?cash[key];
????for?(const?n?of?nums)?{
????????res?=?await?caculate(res,?n);
????}
????cash[key]?=?res;
????return?res;
}
function?caculate(num1:?number,?num2:?number)?{
????return?new?Promise((resolve,?reject)?=>?{
????????asyncAdd(num1,?num2,?(err:?any,?rs:?number)?=>?{
????????????if?(err)?{
????????????????reject(err);
????????????????return;
????????????}
????????????resolve(rs);
????????});
????})
}
復(fù)制代碼
關(guān)于本文
https://juejin.cn/post/7134583899597832200
