JS基礎(chǔ)系列(異步編程解決方案)

大家在js編程中肯定遇到過(guò)異步問(wèn)題,實(shí)踐中大家在異步問(wèn)題上也不斷提出新的解決方案馋艺,這里就梳理一下異步解決方案嚎卫。

回調(diào)函數(shù)

回調(diào)函數(shù)是最原始的解決方案嘉栓,邏輯上很容易理解⊥刂睿看個(gè)例子:

function f1(value, callback) {
    console.log('f1');
    setTimeout(
        () => {
            value = setValue('f2');
            callback.call(this, value);
        }
        , 1000
    );
}
function f2(value){
    console.log(value);
}
function setValue(value) {
    return value;
}

let value;
f1(value, f2);

結(jié)果.png

這里的f2需要f1延時(shí)1s完成value的重新賦值后再打印value侵佃,比較簡(jiǎn)單的處理就是把f2作為f1的回調(diào)函數(shù)。

這是歷史最悠久的方案

  • 優(yōu)點(diǎn):兼容性好
  • 缺點(diǎn):當(dāng)回調(diào)函數(shù)不斷嵌套時(shí)奠支,代碼會(huì)橫向發(fā)展馋辈,形成“意大利面”(Italian noodles)代碼

Promise對(duì)象

隨著歷史的發(fā)展,在前端社區(qū)的某些庫(kù)中有人提出了Promise方案倍谜,后來(lái)這個(gè)方案也被寫入官方標(biāo)準(zhǔn)迈螟。本文不再談各種前端庫(kù)中Promise的實(shí)現(xiàn),只談目前ES規(guī)范中的Promise尔崔。
幾個(gè)要點(diǎn):

  • Promise是一個(gè)原生對(duì)象答毫,我們使用Promise對(duì)象是利用它的API。
  • Promise對(duì)象的異步操作有三種狀態(tài):pending(進(jìn)行中)季春、fulfilled(已成功)和rejected(已失斚绰А)≡嘏回調(diào)函數(shù)只在fulfilled(已成功)和rejected(已失斣拍础)狀態(tài)下觸發(fā)。
  • Promise對(duì)象的實(shí)例可以調(diào)用then方法指定不同狀態(tài)的回調(diào)函數(shù)(或catch方法注冊(cè)rejected(已失斢罟ァ)狀態(tài)的回調(diào)函數(shù))

使用Promise對(duì)象重新實(shí)現(xiàn)上文的例子:

let value;
function f1() {
    console.log('f1');
    const promise = new Promise((resolve, reject) => {
        setTimeout(
            () => {
                let value = setValue('f2');
                if (!!value) {
                    resolve(value);
                } else {
                    reject('error');
                }
            }
            , 1000);
    });
    return promise;
}
function f2(value){
    console.log(value);
}
function setValue(value) {
    return value;
}

f1()
    .then(
        f2,
        (error) => console.log(error)
    );

結(jié)果.png

可以看到函數(shù)f1返回了一個(gè)Promise對(duì)象的實(shí)例惫叛,這個(gè)實(shí)例就具有.then/.catch等方法注冊(cè)回調(diào)函數(shù)。
再看一個(gè)使用Promise實(shí)現(xiàn)的AJAX封裝

function fetchRemote(url, method='GET', param=null) {
    const promise = new Promise(function(resolve, reject) {
        let xhr = new XMLHttpRequest();
        xhr.open(method, url, true);
        xhr.setRequestHeader("Accept", "application/json");
        xhr.onreadystatechange = function() {
            if (xhr.readyState !== 4){
                return;
            }
            if (xhr.status === 200 || xhr.status === 304) {
                resolve(xhr.response);
            } else {
                reject(xhr.status);
            }
        };
        xhr.send(param);
    });
    return promise;
}

// 調(diào)用
fetchRemote(url)
    .then(
        (response) => {
            todo... 
        }
    )
    .catch(
        (error) => {
            todo...
        }
    )

這里提一個(gè)小細(xì)節(jié): 請(qǐng)注意XMLHttpRequest對(duì)象的實(shí)例具有實(shí)例方法onreadystatechange()尺碰,實(shí)際上這是一個(gè)注冊(cè)回調(diào)函數(shù)的api挣棕,每一次XMLHttpRequest實(shí)例對(duì)象的readyState/status被改變時(shí)译隘,回調(diào)函數(shù)都會(huì)被異步觸發(fā)

此外,Promise對(duì)象也提供了一些新API適應(yīng)不同場(chǎng)景洛心,例如.race/.all固耘。

    // Promise.race() 適用于等待多個(gè)promise第一個(gè)完成的場(chǎng)景
    Promise.race(task1, task2, task3...).then().catch();
    
    // Promise.all() 適用于等待多個(gè)promise同時(shí)完成的場(chǎng)景
    Promise.all(task1, task2, task3...).then().catch();

Generator函數(shù)

學(xué)習(xí)過(guò)Generator函數(shù)的同學(xué)應(yīng)該知道厅目,Generator函數(shù)最大的特點(diǎn)就是不會(huì)順序執(zhí)行到底,遇到yield語(yǔ)句時(shí)會(huì)暫停法严,直到調(diào)用next方法才會(huì)繼續(xù)執(zhí)行。顯然深啤,這種特性很適合異步場(chǎng)景。
還是實(shí)現(xiàn)最初的小例子:

const setValue = value => value;
let value;
function* f1() {
    console.log('f1');
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            value = setValue('f2');
            if (!!value) {
                resolve(value);
            } else {
                reject('error');
            }
        }, 1000);
    });
}
function f2 (value) {
    console.log(value);
}

let g = f1();
g.next().value
    .then(f2)
    .catch((error) => {console.error(error);});
結(jié)果.png

再看一個(gè)遠(yuǎn)程請(qǐng)求的例子:

function* fetchData(url) {
    const data = yield fetch(url);
    yield handler(data);
} 

const g = fetchData('http://url/api');
g.next();
g.next();

可以看到溯街,通過(guò)使用Generator函數(shù)的屬性,異步邏輯變得非常簡(jiǎn)單呈昔,只需要在異步等待的函數(shù)處“暫突拥龋”即可。這種語(yǔ)法在redux-saga的庫(kù)中有很好的應(yīng)用堤尾。

async函數(shù)

async函數(shù)顧名思義肝劲,就是為解決異步場(chǎng)景而生的。
async函數(shù)返回的是一個(gè)Promise對(duì)象郭宝,所以async函數(shù)返回的結(jié)果可以調(diào)用.then()/.catch()方法辞槐,本質(zhì)上和promise對(duì)象用法類似,只是有了語(yǔ)法糖書寫更簡(jiǎn)便剩蟀。
還是這個(gè)熟悉的老例子:

const setValue = value => value;
let value;
function f1() {
    console.log('f1');
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            value = setValue(null);
            if (!!value) {
                resolve(value);
            } else {
                reject('error');
            }
        }, 1000);
    });
}
function f2 (value) {
    console.log(value);
}

async function fn() {
    return await f1();
}
fn()
    .then(
        (value) => {console.log(value);}
    )
    .catch(
        (error) => console.error(error)
    );

結(jié)果.png

簡(jiǎn)單解釋一下催蝗,asyncawait需要互相配合,await只能在async函數(shù)中使用育特,await等的是promise對(duì)象丙号,如果await得到的不是promise對(duì)象則會(huì)把普通對(duì)象直接作為promisevalue并更新?tīng)顟B(tài)為resolved
可以很明顯看出缰冤,一般情況下async函數(shù)不具有優(yōu)勢(shì)犬缨,它的優(yōu)勢(shì)在處理then鏈。
看個(gè)例子:

async function manycbs() {
    const arg1 = 'xxx';
    const arg2 = await step1(arg1);
    const arg3 = await step2(arg1, arg2);
    const result = await step3(arg1, arg2, arg3);
    console.log(result);
}

manycbs();

這樣就可以看出async函數(shù)可以把異步寫得像同步一樣棉浸。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怀薛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子迷郑,更是在濱河造成了極大的恐慌枝恋,老刑警劉巖创倔,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異焚碌,居然都是意外死亡畦攘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門十电,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)知押,“玉大人,你說(shuō)我怎么就攤上這事鹃骂√ǘⅲ” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵畏线,是天一觀的道長(zhǎng)静盅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)象踊,這世上最難降的妖魔是什么温亲? 我笑而不...
    開(kāi)封第一講書人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任棚壁,我火速辦了婚禮杯矩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘史隆。我一直安慰自己,他們只是感情好泌射,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布熔酷。 她就那樣靜靜地躺著,像睡著了一般拒秘。 火紅的嫁衣襯著肌膚如雪臭猜。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,549評(píng)論 1 312
  • 那天羹应,我揣著相機(jī)與錄音次屠,去河邊找鬼雳刺。 笑死裸违,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的累颂。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼料饥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼朱监!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起巡蘸,我...
    開(kāi)封第一講書人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤擂送,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后嘹吨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碰纬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年悦析,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了此衅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酌泰,死狀恐怖匕累,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情欢嘿,我是刑警寧澤也糊,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布狸剃,位于F島的核電站,受9級(jí)特大地震影響钞馁,放射性物質(zhì)發(fā)生泄漏匿刮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一熟丸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绩鸣,春花似錦、人聲如沸呀闻。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)勘纯。三九已至驳遵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堤结,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工鸭丛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鳞溉。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像熟菲,于是被迫代替她去往敵國(guó)和親朴恳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子允蚣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 異步編程對(duì)JavaScript語(yǔ)言太重要嚷兔。Javascript語(yǔ)言的執(zhí)行環(huán)境是“單線程”的森渐,如果沒(méi)有異步編程冒晰,根本...
    呼呼哥閱讀 7,313評(píng)論 5 22
  • 弄懂js異步 講異步之前,我們必須掌握一個(gè)基礎(chǔ)知識(shí)-event-loop翩剪。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,714評(píng)論 0 5
  • 你不知道JS:異步 第三章:Promises 接上篇3-1 錯(cuò)誤處理(Error Handling) 在異步編程中...
    purple_force閱讀 1,400評(píng)論 0 2
  • Promise 對(duì)象 Promise 的含義 Promise 是異步編程的一種解決方案前弯,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,711評(píng)論 1 56
  • 如過(guò)眼云煙,我的婚姻已安靜地走過(guò)十個(gè)年頭询枚,未觸碰七年之癢,沒(méi)有左手摸右手的索然金蜀,就算相互依偎著也能甜蜜地流連于彼此...
    果人閱讀 124評(píng)論 0 0