由一道JS非同步面試題,思考對非同步問題的處理(1,暫時不用promise)

weixin_33686714發表於2017-12-11

問題圖示如下

再進一步說明問題

  • 按鈕A按了之後,ajax請求的資料顯示在input type=text框裡,B按鈕也是。

  • 問題就是如果先按A,此時ajax發出去了,但是資料還沒返回來, 我們等不及了,馬上按B按鈕,結果此時A按鈕請求的資料先回來,這就尷尬了,按的b按鈕,結果先顯示A按鈕返回的資料,怎麼解決?

這個問題的解釋在我之前寫的一篇文章,其中的第二題就是解決方https://juejin.im/post/5a1810b56fb9a0452405854c,我們今天把這個問題展開

一、js的非同步的執行機制是什麼

以下是一張解釋非同步佇列的圖,以及文字說明(摘自阮一峰老師的部落格)

)

(1)所有同步任務都在主執行緒上執行,形成一個執行棧(execution context stack)。
(2)主執行緒之外,還存在一個"任務佇列"(task queue)。只要非同步任務有了執行結果,就在"任務佇列"之中放置一個事件。
(3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務佇列",看看裡面有哪些事件。那些對應的非同步任務,於是結束等待狀態,進入執行棧,開始執行。
(4)主執行緒不斷重複上面的第三步。
複製程式碼

這只是針對跟我一樣中級水平或偏下的人普及一下JS非同步的執行原理。之後,我們來看一個非同步引發的問題程式碼

var res = [];  
  
function response(data) {  
    res.push( data );  
}  
  
// ajax(..)是某個庫中提供的某個Ajax函式  
ajax( "http://some.url.1", response );  
ajax( "http://some.url.2", response );    
複製程式碼

問題來了,我們假定期望的行為是res[0] 中放呼叫"http://some.url.1" 的結果,res[1] 中放呼叫"http://some.url.2" 的結果,改怎麼辦呢? 解決辦法如下

var res = [];  
function response(data) {  
    if (data.url == "http://some.url.1") {  
        res[0] = data;  
    }  
  
    else if (data.url == "http://some.url.2") {  
        res[1] = data;  
    }  
}  
  
// ajax(..)是某個庫中提供的某個Ajax函式  
ajax( "http://some.url.1", response );  
ajax( "http://some.url.2", response );  
複製程式碼

上面這個場景用於多個併發函式共享DOM的問題,所以回撥函式可以改進成這樣的寫法

var res = [];  
function response(data) {  
    if (data.url == "http://some.url.1") {  
        //執行操作的函式,把引數data傳入進去
        callbackA(data)
    }  
  
    else if (data.url == "http://some.url.2") {  
        //執行操作的函式,把引數data傳入進去
        callbackB(data)
    }  
}  
  
// ajax(..)是某個庫中提供的某個Ajax函式  
ajax( "http://some.url.1", response );  
ajax( "http://some.url.2", response );  
複製程式碼

好了,我們總結出一種處理併發共享DOM問題的解決方案,這個方案是我在一本叫《你不知道javascript 中卷》看到的,考官繼續說,不用這種方法,因為要依賴後端發的資料要包含data.url,也就是url這個屬性,怎樣不依賴後端,前端獨立解決這個問題呢? 在response上我們做一下改動,設定一個全域性變數

var status;  //值是undefined
複製程式碼

我們在點選A按鈕的時候, 讓status的值變為A

status = "A";
複製程式碼

我們在點選B按鈕的時候,讓status的值變為B

status = "B";
複製程式碼

也就是resopnse改成這樣:


function response(data) {  
    var status;
    if(status = "A") {
        // 點選A按鈕status就變為“A”,所以不會執行按鈕B的回撥函式
        執行 callbackA() A按鈕的回撥函式
    }else if(status = "B"){
        // 點選B按鈕status就變為“B”,所以不會執行按鈕A的回撥函式
        執行 callbackB() B按鈕的回撥函式
}
}  
複製程式碼

這樣就解決了點A只顯示A的資料,點B只顯示B的資料的問題。

在這裡我們繼續延伸這個話題, 請看以下場景

var a,b;
function foo(x) {
    a = x * 2;
    baz();
}

function bar(y) {
    b = y * 2;
    baz()
}

function baz() {
    console.log(a+b)
}

// ajax(..)是某個庫中提供的某個Ajax函式  
ajax( "http://some.url.1", response );  
ajax( "http://some.url.2", response ); 
複製程式碼

我們的目的是等a,b都非同步請求回來才執行baz,解決辦法如下

var a,b;

function foo(x) {
    a = x * 2;
    if(a && b) {
        baz();
    }
}

function bar(y) {
    b = y * 2;
    if(a && b) {
        baz();
    }
}

function baz() {
    console.log(a+b)
}

// ajax(..)是某個庫中提供的某個Ajax函式  
ajax( "http://some.url.1", response );  
ajax( "http://some.url.2", response ); 
複製程式碼

接著,我們再換一個場景, 請看以下程式碼

var a;

function foo(x) {
    
    a = x * 2
    baz();
 
}

function bar(x) {
    a = x / 2;
    baz();
}

function baz() {
    console.log(a)
}

// ajax(..)是某個庫中提供的某個Ajax函式  
ajax( "http://some.url.1", response );  
ajax( "http://some.url.2", response ); 

複製程式碼

a的值會改變兩次,需求是隻讓a變一次,也就是第一次改變,第二次就不改變了

var a;
function foo(x) {
    if(!a) {
    a = x * 2;
    baz();
    }
}

function bar(y) {
    if(!a) {
    a = x / 2;
    baz();
    }
}

function baz() {
    console.log(a)
}

// ajax(..)是某個庫中提供的某個Ajax函式  
ajax( "http://some.url.1", response );  
ajax( "http://some.url.2", response ); 
複製程式碼

好了,下面我要寫一篇用promise解決非同步問題的隨筆(曾經看了一篇關於原生實現promise的文章,到時候也會簡單介紹下一個簡單的,但不是完全體的promise實現程式碼,只是為了大家更容易理解promise實現的內部大致原理),題目如下,需要20張圖片,每次發出10個非同步請求,請求10張圖片,所以一共要請求兩次,而且要求每次請求的10張圖片是按順序接收的,比如第一次發的請求是請求趙麗穎的圖片,第二次發的請求是請求張三瘋的圖片,要求接收的順序也是趙麗穎圖片,張三瘋圖片 ...以此類推。

相關文章