1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        詳解JS四種異步解決方案:回調(diào)函數(shù)、Promise、Generator、async/await

        共 9436字,需瀏覽 19分鐘

         ·

        2022-05-13 09:57


        同步&異步的概念

        在講這四種異步方案之前,我們先來明確一下同步和異步的概念:

        所謂同步(synchronization),簡單來說,就是順序執(zhí)行,指的是同一時間只能做一件事情,只有目前正在執(zhí)行的事情做完之后,才能做下一件事情。比如咱們?nèi)セ疖囌举I票,假設(shè)窗口只有1個,那么同一時間只能處理1個人的購票業(yè)務(wù),其余的需要進(jìn)行排隊。這種one by one的動作就是同步。同步操作的優(yōu)點在于做任何事情都是依次執(zhí)行,井然有序,不會存在大家同時搶一個資源的問題。同步操作的缺點在于會阻塞后續(xù)代碼的執(zhí)行。如果當(dāng)前執(zhí)行的任務(wù)需要花費很長的時間,那么后面的程序就只能一直等待。從而影響效率,對應(yīng)到前端頁面的展示來說,有可能會造成頁面渲染的阻塞,大大影響用戶體驗。

        所謂異步(Asynchronization),指的是當(dāng)前代碼的執(zhí)行不影響后面代碼的執(zhí)行。當(dāng)程序運(yùn)行到異步的代碼時,會將該異步的代碼作為任務(wù)放進(jìn)任務(wù)隊列,而不是推入主線程的調(diào)用棧。等主線程執(zhí)行完之后,再去任務(wù)隊列里執(zhí)行對應(yīng)的任務(wù)即可。因此,異步操作的優(yōu)點就是不會阻塞后續(xù)代碼的執(zhí)行。

        js中異步的應(yīng)用場景

        開篇講了同步和異步的概念,那么在JS中異步的應(yīng)用場景有哪些呢?

        • 定時任務(wù):setTimeout、setInterval
        • 網(wǎng)絡(luò)請求:ajax請求、動態(tài)創(chuàng)建img標(biāo)簽的加載
        • 事件監(jiān)聽器:addEventListener

        實現(xiàn)異步的四種方法

        對于setTimeout、setInterval、addEventListener這種異步場景,不需要我們手動實現(xiàn)異步,直接調(diào)用即可。但是對于ajax請求、node.js中操作數(shù)據(jù)庫這種異步,就需要我們自己來實現(xiàn)了~

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

        在微任務(wù)隊列出現(xiàn)之前,JS實現(xiàn)異步的主要方式就是通過回調(diào)函數(shù)。以一個簡易版的Ajax請求為例,代碼結(jié)構(gòu)如下所示:

        function?ajax(obj){
        ?let?default?=?{
        ???url:?'...',
        ???type:'GET',
        ???async:true,
        ???contentType:?'application/json',
        ???success:function(){}
        ????};

        ?for?(let?key?in?obj)?{
        ????????defaultParam[key]?=?obj[key];
        ????}

        ????let?xhr;
        ????if?(window.XMLHttpRequest)?{
        ????????xhr?=?new?XMLHttpRequest();
        ????}?else?{
        ????????xhr?=?new?ActiveXObject('Microsoft.XMLHTTP');
        ????}
        ????
        ????xhr.open(defaultParam.type,?defaultParam.url+'?'+dataStr,?defaultParam.async);
        ????xhr.send();
        ????xhr.onreadystatechange?=?function?(){
        ????????if?(xhr.readyState?===?4){
        ????????????if(xhr.status?===?200){
        ????????????????let?result?=?JSON.parse(xhr.responseText);
        ????????????????//?在此處調(diào)用回調(diào)函數(shù)
        ????????????????defaultParam.success(result);
        ????????????}
        ????????}
        ????}
        }

        復(fù)制代碼

        我們在業(yè)務(wù)代碼里可以這樣調(diào)用ajax請求:

        ajax({
        ???url:'#',
        ???type:GET,
        ???success:function(e){
        ????//?回調(diào)函數(shù)里就是對請求結(jié)果的處理
        ???}
        });
        復(fù)制代碼

        ajax的success方法就是一個回調(diào)函數(shù),回調(diào)函數(shù)中執(zhí)行的是我們請求成功之后要做的進(jìn)一步操作。這樣就初步實現(xiàn)了異步,但是回調(diào)函數(shù)有一個非常嚴(yán)重的缺點,那就是回調(diào)地獄的問題。大家可以試想一下,如果我們在回調(diào)函數(shù)里再發(fā)起一個ajax請求呢?那豈不是要在success函數(shù)里繼續(xù)寫一個ajax請求?那如果需要多級嵌套發(fā)起ajax請求呢?豈不是需要多級嵌套?如果嵌套的層級很深的話,我們的代碼結(jié)構(gòu)可能就會變成這樣:因此,為了解決回調(diào)地獄的問題,提出了Promise、async/await、generator的概念。

        2、Promise

        Promise作為典型的微任務(wù)之一,它的出現(xiàn)可以使JS達(dá)到異步執(zhí)行的效果。一個Promise函數(shù)的結(jié)構(gòu)如下列代碼如下:

        const?promise?=?new?Promise((resolve,?reject)?=>?{
        ?resolve('a');
        });
        promise
        ????.then((arg)?=>?{?console.log(`執(zhí)行resolve,參數(shù)是${arg}`)?})
        ????.catch((arg)?=>?{?console.log(`執(zhí)行reject,參數(shù)是${arg}`)?})
        ????.finally(()?=>?{?console.log('結(jié)束promise')?});
        復(fù)制代碼

        如果,我們需要嵌套執(zhí)行異步代碼,相比于回調(diào)函數(shù)來說,Promise的執(zhí)行方式如下列代碼所示:

        const?promise?=?new?Promise((resolve,?reject)?=>?{
        ?resolve(1);
        });
        promise.then((value)?=>?{
        ?????console.log(value);
        ?????return?value?*?2;
        ????}).then((value)?=>?{
        ?????console.log(value);
        ?????return?value?*?2;
        ????}).then((value)?=>?{
        ????console.log(value);
        ????}).catch((err)?=>?{
        ??console.log(err);
        ????});
        復(fù)制代碼

        即,通過then來實現(xiàn)多級嵌套(鏈?zhǔn)秸{(diào)用),這看起來是不是就比回調(diào)函數(shù)舒服多了~

        每個Promise都會經(jīng)歷的生命周期是:

        • 進(jìn)行中(pending) - 此時代碼執(zhí)行尚未結(jié)束,所以也叫未處理的(unsettled)
        • 已處理(settled) - 異步代碼已執(zhí)行結(jié)束 已處理的代碼會進(jìn)入兩種狀態(tài)中的一種:
          • 已完成(fulfilled) - 表明異步代碼執(zhí)行成功,由resolve()觸發(fā)
          • 已拒絕(rejected)- 遇到錯誤,異步代碼執(zhí)行失敗 ,由reject()觸發(fā)

        因此,pending,fulfilled, rejected就是Promise中的三種狀態(tài)啦~ 大家一定要牢記,在Promise中,要么包含resolve()來表示Promise的狀態(tài)為fulfilled,要么包含reject()來表示Promise的狀態(tài)為rejected。不然我們的Promise就會一直處于pending的狀態(tài),直至程序崩潰...

        除此之外,Promise不僅很好的解決了鏈?zhǔn)秸{(diào)用的問題,它還有很多神奇的操作呢:

        • **Promise.all(promises)**:接收一個包含多個Promise對象的數(shù)組,等待所有都完成時,返回存放它們結(jié)果的數(shù)組。如果任一被拒絕,則立即拋出錯誤,其他已完成的結(jié)果會被忽略
        • Promise.allSettled(promises): 接收一個包含多個Promise對象的數(shù)組,等待所有都已完成或者已拒絕時,返回存放它們結(jié)果對象的數(shù)組。每個結(jié)果對象的結(jié)構(gòu)為{status:'fulfilled' // 或 'rejected', value // 或reason}
        • Promise.race(promises): 接收一個包含多個Promise對象的數(shù)組,等待第一個有結(jié)果(完成/拒絕)的Promise,并把其result/error作為結(jié)果返回
        function?getPromises(){
        ????return?[
        ????????new?Promise(((resolve,?reject)?=>?setTimeout(()?=>?resolve(1),?1000))),
        ????????new?Promise(((resolve,?reject)?=>?setTimeout(()?=>?reject(new?Error('2')),?2000))),
        ????????new?Promise(((resolve,?reject)?=>?setTimeout(()?=>?resolve(3),?3000))),
        ????];
        }

        Promise.all(getPromises()).then(console.log);
        Promise.allSettled(getPromises()).then(console.log);
        Promise.race(getPromises()).then(console.log);
        復(fù)制代碼

        打印結(jié)果如下:

        3、Generator

        Generator是ES6提出的一種異步編程的方案。因為手動創(chuàng)建一個iterator十分麻煩,因此ES6推出了generator,用于更方便的創(chuàng)建iterator。也就是說,Generator就是一個返回值為iterator對象的函數(shù)。
        在講Generator之前,我們先來看看iterator是什么:
        iterator是什么?
        iterator中文名叫迭代器。它為js中各種不同的數(shù)據(jù)結(jié)構(gòu)(Object、Array、Set、Map)提供統(tǒng)一的訪問機(jī)制。任何數(shù)據(jù)結(jié)構(gòu)只要部署了Iterator接口,就可以完成遍歷操作。 因此iterator也是一種對象,不過相比于普通對象來說,它有著專為迭代而設(shè)計的接口。

        iterator 的作用:

        • 為各種數(shù)據(jù)結(jié)構(gòu),提供一個統(tǒng)一的、簡便的訪問接口;
        • 使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列;
        • ES6 創(chuàng)造了一種新的遍歷命令for…of循環(huán),Iterator 接口主要供for…of消費

        iterator的結(jié)構(gòu): 它有next方法,該方法返回一個包含valuedone兩個屬性的對象(我們假設(shè)叫result)。value是迭代的值,后者是表明迭代是否完成的標(biāo)志。true表示迭代完成,false表示沒有。iterator內(nèi)部有指向迭代位置的指針,每次調(diào)用next,自動移動指針并返回相應(yīng)的result。

        原生具備iterator接口的數(shù)據(jù)結(jié)構(gòu)如下:

        • Array
        • Map
        • Set
        • String
        • TypedArray
        • 函數(shù)里的arguments對象
        • NodeList對象

        這些數(shù)據(jù)結(jié)構(gòu)都有一個Symbol.iterator屬性,可以直接通過這個屬性來直接創(chuàng)建一個迭代器。也就是說,Symbol.iterator屬性只是一個用來創(chuàng)建迭代器的接口,而不是一個迭代器,因為它不含遍歷的部分。
        使用Symbol.iterator接口生成iterator迭代器來遍歷數(shù)組的過程為:

        let?arr?=?['a','b','c'];

        let?iter?=?arr[Symbol.iterator]();

        iter.next()?//?{?value:?'a',?done:?false?}
        iter.next()?//?{?value:?'b',?done:?false?}
        iter.next()?//?{?value:?'c',?done:?false?}
        iter.next()?//?{?value:?undefined,?done:?true?}
        復(fù)制代碼

        for ... of的循環(huán)內(nèi)部實現(xiàn)機(jī)制其實就是iterator,它首先調(diào)用被遍歷集合對象的 Symbol.iterator 方法,該方法返回一個迭代器對象,迭代器對象是可以擁有.next()方法的任何對象,然后,在 for ... of 的每次循環(huán)中,都將調(diào)用該迭代器對象上的 .next 方法。然后使用for i of打印出來的i也就是調(diào)用.next方法后得到的對象上的value屬性。

        對于原生不具備iterator接口的數(shù)據(jù)結(jié)構(gòu),比如Object,我們可以采用自定義的方式來創(chuàng)建一個遍歷器。

        比如,我們可以自定義一個iterator來遍歷對象:

        let?obj?=?{a:?"hello",?b:?"world"};
        //?自定義迭代器
        function?createIterator(items)?{
        ????let?keyArr?=?Object.keys(items);
        ????let?i?=?0;
        ????return?{
        ????????next:?function?()?{
        ????????????let?done?=?(i?>=?keyArr.length);
        ????????????let?value?=?!done???items[keyArr[i++]]?:?undefined;
        ????????????return?{
        ????????????????value:?value,
        ????????????????done:?done,
        ????????????};
        ????????}
        ????};
        }

        let?iterator?=?createIterator(obj);
        console.log(iterator.next());?//?"{?value:?'hello',?done:?false?}"
        console.log(iterator.next());??//?"{?value:?'world',?done:?false?}"
        console.log(iterator.next());??//?"{?value:?undefined,?done:?true?}"
        復(fù)制代碼

        接下來,我們來聊聊Generator:
        我們通過一個例子來看看Gnerator的特征:

        function*?createIterator()?{
        ??yield?1;
        ??yield?2;
        ??yield?3;
        }
        //?generators可以像正常函數(shù)一樣被調(diào)用,不同的是會返回一個?iterator
        let?iterator?=?createIterator();
        console.log(iterator.next().value);?//?1
        console.log(iterator.next().value);?//?2
        console.log(iterator.next().value);?//?3
        復(fù)制代碼

        Generator 函數(shù)是 ES6 提供的一種異步編程解決方案。形式上,Generator 函數(shù)是一個普通函數(shù),但是有兩個特征:

        • function關(guān)鍵字與函數(shù)名之間有一個星號
        • 函數(shù)體內(nèi)部使用yield語句,定義不同的內(nèi)部狀態(tài)

        Generator函數(shù)的調(diào)用方法與普通函數(shù)一樣,也是在函數(shù)名后面加上一對圓括號。不同的是,調(diào)用Generator函數(shù)后,該函數(shù)并不執(zhí)行,返回的也不是函數(shù)運(yùn)行結(jié)果,而是一個指向內(nèi)部狀態(tài)的指針對象,也就是遍歷器對象(Iterator Object

        打印看看Generator函數(shù)返回值的內(nèi)容:發(fā)現(xiàn)generator函數(shù)的返回值的原型鏈上確實有iterator對象該有的next,這充分說明了generator的返回值是一個iterator。除此之外還有函數(shù)該有的return方法和throw方法。

        在普通函數(shù)中,我們想要一個函數(shù)最終的執(zhí)行結(jié)果,一般都是return出來,或者以return作為結(jié)束函數(shù)的標(biāo)準(zhǔn)。運(yùn)行函數(shù)時也不能被打斷,期間也不能從外部再傳入值到函數(shù)體內(nèi)。但在generator中,就打破了這幾點,所以generator和普通的函數(shù)完全不同。當(dāng)以function*的方式聲明了一個Generator生成器時,內(nèi)部是可以有許多狀態(tài)的,以yield進(jìn)行斷點間隔。期間我們執(zhí)行調(diào)用這個生成的Generator,他會返回一個遍歷器對象,用這個對象上的方法,實現(xiàn)獲得一個yield后面輸出的結(jié)果。

        function*?generator()?{
        ????yield?1
        ????yield?2
        };
        let?iterator?=?generator();
        iterator.next()??//?{value:?1,?done:?false}
        iterator.next()??//?{value:?2,?done:?false}
        iterator.next()??//?{value:?undefined,?done:?true}
        復(fù)制代碼

        yield和return的區(qū)別:

        • 都能返回緊跟在語句后面的那個表達(dá)式的值
        • yield相比于return來說,更像是一個斷點。遇到y(tǒng)ield,函數(shù)暫停執(zhí)行,下一次再從該位置繼續(xù)向后執(zhí)行,而return語句不具備位置記憶的功能。
        • 一個函數(shù)里面,只能執(zhí)行一個return語句,但是可以執(zhí)行多次yield表達(dá)式。
        • 正常函數(shù)只能返回一個值,因為只能執(zhí)行一次return;Generator 函數(shù)可以返回一系列的值,因為可以有任意多個yield

        語法注意點:

        • yield表達(dá)式只能用在 Generator 函數(shù)里面

        • yield表達(dá)式如果用在另一個表達(dá)式之中,必須放在圓括號里面

        • yield表達(dá)式用作函數(shù)參數(shù)或放在賦值表達(dá)式的右邊,可以不加括號。

        • 如果 return 語句后面還有 yield 表達(dá)式,那么后面的 yield 完全不生效

        使用Generator的其余注意事項:

        • 需要注意的是,yield 不能跨函數(shù)。并且yield需要和*配套使用,別處使用無效
        function*?createIterator(items)?{
        ??items.forEach(function?(item)?{
        ????//?語法錯誤
        ????yield?item?+?1;
        ??});
        }
        復(fù)制代碼
        • 箭頭函數(shù)不能用做 generator

        講了這么多,那么Generator到底有什么用呢?

        • 因為Generator可以在執(zhí)行過程中多次返回,所以它看上去就像一個可以記住執(zhí)行狀態(tài)的函數(shù),利用這一點,寫一個generator就可以實現(xiàn)需要用面向?qū)ο蟛拍軐崿F(xiàn)的功能。
        • Generator還有另一個巨大的好處,就是把異步回調(diào)代碼變成“同步”代碼。這個在ajax請求中很有用,避免了回調(diào)地獄.

        4、 async/await

        最后我們來講講async/await,終于講到這兒了!??!async/await是ES7提出的關(guān)于異步的終極解決方案。我看網(wǎng)上關(guān)于async/await是誰的語法糖這塊有兩個版本:

        • 第一個版本說async/await是Generator的語法糖
        • 第二個版本說async/await是Promise的語法糖

        其實,這兩種說法都沒有錯。關(guān)于async/await是Generator的語法糖: 所謂Generator語法糖,表明的就是aysnc/await實現(xiàn)的就是generator實現(xiàn)的功能。但是async/await比generator要好用。因為generator執(zhí)行yield設(shè)下的斷點采用的方式就是不斷的調(diào)用iterator方法,這是個手動調(diào)用的過程。針對generator的這個缺點,后面提出了co這個庫函數(shù)來自動執(zhí)行next,相比于之前的方案,這種方式確實有了進(jìn)步,但是仍然麻煩。而async配合await得到的就是斷點執(zhí)行后的結(jié)果。因此async/await比generator使用更普遍。

        總結(jié)下來,async函數(shù)對 Generator函數(shù)的改進(jìn),主要體現(xiàn)在以下三點:

        • 內(nèi)置執(zhí)行器:Generator函數(shù)的執(zhí)行必須靠執(zhí)行器,因為不能一次性執(zhí)行完成,所以之后才有了開源的 co函數(shù)庫。但是,async函數(shù)和正常的函數(shù)一樣執(zhí)行,也不用 co函數(shù)庫,也不用使用 next方法,而 async函數(shù)自帶執(zhí)行器,會自動執(zhí)行。
        • 適用性更好:co函數(shù)庫有條件約束,yield命令后面只能是 Thunk函數(shù)或 Promise對象,但是 async函數(shù)的 await關(guān)鍵詞后面,可以不受約束。
        • 可讀性更好:async和 await,比起使用 *號和 yield,語義更清晰明了。

        關(guān)于async/await是Promise的語法糖: 如果不使用async/await的話,Promise就需要通過鏈?zhǔn)秸{(diào)用來依次執(zhí)行then之后的代碼:

        function?counter(n){
        ?return?new?Promise((resolve,?reject)?=>?{?
        ????resolve(n?+?1);
        ????});
        }

        function?adder(a,?b){
        ????return?new?Promise((resolve,?reject)?=>?{?
        ????resolve(a?+?b);
        ????});
        }

        function?delay(a){
        ????return?new?Promise((resolve,?reject)?=>?{?
        ????setTimeout(()?=>?resolve(a),?1000);
        ????});
        }
        //?鏈?zhǔn)秸{(diào)用寫法
        function?callAll(){
        ????counter(1)
        ???????.then((val)?=>?adder(val,?3))
        ???????.then((val)?=>?delay(val))
        ???????.then(console.log);
        }
        callAll();//5
        復(fù)制代碼

        雖然相比于回調(diào)地獄來說,鏈?zhǔn)秸{(diào)用確實順眼多了。但是其呈現(xiàn)仍然略繁瑣了一些。而async/await的出現(xiàn),就使得我們可以通過同步代碼來達(dá)到異步的效果

        async?function?callAll(){
        ???const?count?=?await?counter(1);
        ???const?sum?=?await?adder(count?+?3);
        ???console.log(await?delay(sum));
        }
        callAll();//?5
        復(fù)制代碼

        由此可見,Promise搭配async/await的使用才是正解!

        總結(jié)

        • promise讓異步執(zhí)行看起來更清晰明了,通過then讓異步執(zhí)行結(jié)果分離出來。
        • async/await其實是基于Promise的。async函數(shù)其實是把promise包裝了一下。使用async函數(shù)可以讓代碼簡潔很多,不需要promise一樣需要些then,不需要寫匿名函數(shù)處理promise的resolve值,也不需要定義多余的data變量,還避免了嵌套代碼。
        • async函數(shù)是Generator函數(shù)的語法糖。async函數(shù)的返回值是 promise 對象,這比 Generator 函數(shù)的返回值是 Iterator 對象方便多了。同時,我們還可以用await來替代then方法指定下一步的操作。
        • 感覺Promise+async的操作最為常見。因為Generator被async替代了呀~

        關(guān)于本文

        作者:DoubleSweet

        https://juejin.cn/post/7082753409060716574


        The End

        瀏覽 28
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            蜜桃视频成人网站入口 | 久久打炮视频 | 亚洲国产第一 | 久久久999国产 | 婷婷五月天视频 | 一级国产国产一级 | 在健身房被教练摸出水h | 91熟女柏欣彤大黑牛自慰 | 中文字幕一区二区三区5566 | 成人欧美精品区二区三 |