欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一篇文章詳解JS的四種異步解決方案

 更新時間:2024年06月20日 11:27:03   作者:chengbo_eva  
異步在JS中是常用的,下面這篇文章主要給大家介紹了關于JS四種異步解決方案的相關資料,文中通過代碼介紹的非常詳細,對大家學習或者使用js具有一定的參考借鑒價值,需要的朋友可以參考下

前言

「異步編程」是前端工程師日常開發(fā)中經常會用到的技術,也是校招面試過程中??嫉囊粋€知識點。

通過掌握「異步編程」的四種方式,可以讓我們能夠更好地處理JavaScript中的異步操作,提高代碼的性能和用戶體驗。

因此,「今天就想和大家來聊聊JS異步編程的四種方式!」

同步&異步的概念

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

        所謂「同步(synchronization)」,簡單來說,就是「順序執(zhí)行」,指的是同一時間只能做一件事情,只有目前正在執(zhí)行的事情做完之后,才能做下一件事情。

        「同步操作的優(yōu)點」在于做任何事情都是依次執(zhí)行,井然有序,不會存在大家同時搶一個資源的問題。

        「同步操作的缺點」在于「會阻塞后續(xù)代碼的執(zhí)行」。如果當前執(zhí)行的任務需要花費很長的時間,那么后面的程序就只能一直等待。

        所謂「異步(Asynchronization)」,指的是當前代碼的執(zhí)行不影響后面代碼的執(zhí)行。當程序運行到異步的代碼時,會將該異步的代碼作為任務放進「任務隊列」,而不是推入主線程的調用棧。等主線程執(zhí)行完之后,再去任務隊列里執(zhí)行對應的任務即可。

        因此,「異步操作的優(yōu)點就是:不會阻塞后續(xù)代碼的執(zhí)行?!?/strong>

js中異步的應用場景

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

  • 「定時任務」:setTimeout、setInterval

  • 「網(wǎng)絡請求」:ajax請求、動態(tài)創(chuàng)建img標簽的加載

  • 「事件監(jiān)聽器」:addEventListener

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

對于「setTimeout、setInterval、addEventListener」這種異步場景,不需要我們手動實現(xiàn)異步,直接調用即可。

但是對于「ajax請求」、「node.js中操作數(shù)據(jù)庫這種異步」,就需要我們自己來實現(xiàn)了~

1、 回調函數(shù)

在微任務隊列出現(xiàn)之前,JS實現(xiàn)異步的主要方式就是通過「回調函數(shù)」。

以一個簡易版的Ajax請求為例,代碼結構如下所示:

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);
                // 在此處調用回調函數(shù)
                defaultParam.success(result);
            }
        }
    }
}

我們在業(yè)務代碼里可以這樣調用「ajax請求」

ajax({
   url:'#',
   type:GET,
   success:function(e){
    // 回調函數(shù)里就是對請求結果的處理
   }
});

「ajax請求」中的success方法就是一個回調函數(shù),回調函數(shù)中執(zhí)行的是我們請求成功之后要做的進一步操作。

這樣就初步實現(xiàn)了異步,但是回調函數(shù)有一個非常嚴重的缺點,那就是「回調地獄」的問題。

大家可以試想一下,如果我們在回調函數(shù)里再發(fā)起一個ajax請求呢?那豈不是要在success函數(shù)里繼續(xù)寫一個ajax請求?那如果需要多級嵌套發(fā)起ajax請求呢?豈不是需要多級嵌套?

如果嵌套的層級很深的話,我們的代碼結構可能就會變成這樣:

因此,為了解決回調地獄的問題,提出了「promise」、「async/await」、「generator」的概念。

2、Promise

「Promise」作為典型的微任務之一,它的出現(xiàn)可以使JS達到異步執(zhí)行的效果。

一個「Promise函數(shù)」的結構如下列代碼如下:

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('結束promise') });

如果我們需要嵌套執(zhí)行異步代碼,相比于回調函數(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);
    });

即通過then來實現(xiàn)多級嵌套(「鏈式調用」),這看起來是不是就比回調函數(shù)舒服多了~

每個「Promise」都會經歷的生命周期是:

  • 進行中(pending) :此時代碼執(zhí)行尚未結束,所以也叫未處理的(unsettled)

    已處理(settled) :異步代碼已執(zhí)行結束 已處理的代碼會進入兩種狀態(tài)中的一種:
    • 已拒絕(rejected):遇到錯誤,異步代碼執(zhí)行失敗 ,由reject()觸發(fā)

    • 已完成(fulfilled):表明異步代碼執(zhí)行成功,由resolve()觸發(fā)

因此,「pending」,「fulfilled」「rejected」就是「Promise」中的三種狀態(tài)啦~

        需要注意的是,在「Promise」中,要么包含resolve() 來表示 「Promise」 的狀態(tài)為fulfilled,要么包含 reject() 來表示「Promise」的狀態(tài)為rejected。

        不然我們的「Promise」就會一直處于pending的狀態(tài),直至程序崩潰...

除此之外,「Promise」不僅很好的解決了鏈式調用的問題,它還有很多高頻的操作:

  • ·Promise.all(promises) :接收一個包含多個Promise對象的數(shù)組,等待所有都完成時,返回存放它們結果的數(shù)組。如果任一被拒絕,則立即拋出錯誤,其他已完成的結果會被忽略

  • ·Promise.allSettled(promises) : 接收一個包含多個Promise對象的數(shù)組,等待所有都已完成或者已拒絕時,返回存放它們結果對象的數(shù)組。每個結果對象的結構為{status:'fulfilled' // 或 'rejected', value // 或reason}

  • ·Promise.race(promises) : 接收一個包含多個Promise對象的數(shù)組,等待第一個有結果(完成/拒絕)的Promise,并把其result/error作為結果返回

示例代碼如下所示:

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);

打印結果為:

3、Generator

「generator」是ES6提出的一種異步編程的方案。因為手動創(chuàng)建一個iterator十分麻煩,因此ES6推出了「generator」,用于更方便的創(chuàng)建iterator。

也就是說,「generator」就是一個返回值為iterator對象的函數(shù)。

在講「generator」之前,我們先來看看iterator是什么:

?

iterator中文名叫「迭代器」。它為js中各種不同的數(shù)據(jù)結構(Object、Array、Set、Map)提供統(tǒng)一的訪問機制。
任何數(shù)據(jù)結構只要部署iterator接口,就可以完成遍歷操作。
因此iterator也是一種對象,不過相比于普通對象來說,它有著專為迭代而設計的接口。

?

我們通過一個例子來看看generator的特征:

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

形式上,「generator」 函數(shù)是一個普通函數(shù),但是有兩個特征:

  • ·function關鍵字與函數(shù)名之間有一個星號

  • ·函數(shù)體內部使用yield語句,定義不同的內部狀態(tài)

在普通函數(shù)中,我們想要一個函數(shù)最終的執(zhí)行結果,一般都是return出來,或者以return作為結束函數(shù)的標準。運行函數(shù)時也不能被打斷,期間也不能從外部再傳入值到函數(shù)體內。

但在「generator」中,就打破了這幾點,所以「generator」和普通的函數(shù)完全不同。

當以function*  的方式聲明了一個「generator」生成器時,內部是可以有許多狀態(tài)的,以yield進行斷點間隔。期間我們執(zhí)行調用這個生成的「generator」,他會返回一個遍歷器對象,用這個對象上的方法,實現(xiàn)獲得一個yield后面輸出的結果。

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}

4、 async/await

最后我們來講講「async/await」,終于講到這兒了?。?!

「async/await」是ES7提出的關于異步的終極解決方案。我看網(wǎng)上關于「async/await」是誰的語法糖這塊有兩個版本:

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

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

其實,這兩種說法都沒有錯。

「關于async/await是Generator的語法糖:」

 所謂generator語法糖,表明的就是「aysnc/await」實現(xiàn)的就是generator實現(xiàn)的功能。但是「async/await」比generator要好用。因為generator執(zhí)行yield設下的斷點采用的方式就是不斷的調用iterator方法,這是個手動調用的過程。

而async配合await得到的就是斷點執(zhí)行后的結果。因此「async/await」比generator使用更普遍。

「關于async/await是Promise的語法糖:」

如果不使用「async/await」的話,Promise就需要通過鏈式調用來依次執(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);
    });
}
// 鏈式調用寫法
function callAll(){
    counter(1)
       .then((val) => adder(val, 3))
       .then((val) => delay(val))
       .then(console.log);
}
callAll();//5

雖然相比于回調地獄來說,鏈式調用確實順眼多了。但是其呈現(xiàn)仍然略繁瑣了一些。

「async/await的出現(xiàn),就使得我們可以通過同步代碼來達到異步的效果」

async function callAll(){
   const count = await counter(1);
   const sum = await adder(count, 3);
   console.log(await delay(sum));
}
callAll();// 5

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

總結

到此這篇關于JS四種異步解決方案的文章就介紹到這了,更多相關JS異步解決方案內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論