成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

我了解到的JavaScript異步編程

RichardXG / 2916人閱讀

摘要:接下來我們看下三類異步編程的實現(xiàn)。事件監(jiān)聽事件發(fā)布訂閱事件監(jiān)聽是一種非常常見的異步編程模式,它是一種典型的邏輯分離方式,對代碼解耦很有用處。

一、 一道面試題

前段時間面試,考察比較多的是js異步編程方面的相關(guān)知識點,如今,正好輪到自己分享技術(shù),所以想把js異步編程學(xué)習(xí)下,做個總結(jié)。
下面這個demo 概括了大多數(shù)面試過程中遇到的問題:

for(var i = 0; i < 3; i++) {
   setTimeout(function() {
       console.log("timeout" + i);
   })
}
 
new Promise(function(resolve) {
    console.log("promise1");
    for(var i = 0; i < 1000; i++) {
        i == 99 && resolve();
    }
    console.log("promise2");
}).then(function() {
    console.log("then1");
})

console.log("global1");

通過驗證可以得知這個demo的結(jié)果為:

可是為什么會是這樣的結(jié)果,我們可能需要先了解下下面兩個知識點

二、 二個前提知識點 2.1 瀏覽器內(nèi)核的多線程

瀏覽器的內(nèi)核是多線程的,他們在內(nèi)核的控制下互相配合以保持同步,一個瀏覽器至少實現(xiàn)三個常駐的線程:javascript引擎線程,GUI渲染線程,瀏覽器事件觸發(fā)線程。

1)js引擎,基于事件驅(qū)動單線程執(zhí)行的,js引擎一直等待著任務(wù)隊列中任務(wù)的到來,然后加以處理,瀏覽器無論什么時候都只有一個JS線程在運(yùn)行JS程序。
2)GUI線程,當(dāng)界面需要重繪或由于某種操作引發(fā)回流時,該線程就會執(zhí)行。它和JS引擎是互斥的。
3)瀏覽器事件觸發(fā)線程,當(dāng)一個事件被觸發(fā)時,該線程會把事件添加到待處理隊列的隊尾,等待js引擎的處理,這些事件可來自JavaScript引擎當(dāng)前執(zhí)行的代碼塊如,setTimeOut, 也可以來自瀏覽器內(nèi)核的其他線程如鼠標(biāo)點擊,AJAX異步請求等,但由于JS的單線程關(guān)系,所有這些事件都得排隊等待JS引擎處理。

2.2 事件循環(huán)機(jī)制

1)任務(wù)隊列又分為macro-task(宏任務(wù))與micro-task(微任務(wù)),
在最新標(biāo)準(zhǔn)中,它們被分別稱為task與jobs。

2)macro-task大概包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。

3)micro-task【先執(zhí)行】大概包括: process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)

setTimeout/Promise等我們稱之為任務(wù)源。而進(jìn)入任務(wù)隊列的是他們指定的具體執(zhí)行任務(wù)。

事件循環(huán)的順序,決定了JavaScript代碼的執(zhí)行順序。它從script(整體代碼)開始第一次循環(huán)。之后全局上下文進(jìn)入函數(shù)調(diào)用棧。直到調(diào)用棧清空(只剩全局),然后執(zhí)行所有的micro-task。當(dāng)所有可執(zhí)行的micro-task執(zhí)行完畢之后。循環(huán)再次從macro-task開始,找到其中一個任務(wù)隊列執(zhí)行完畢,然后再執(zhí)行所有的macro-task,這樣一直循環(huán)下去。

通過這個事件循環(huán)的順序,我們就知道,為什么上面提到的面試題為什么是這樣的輸出結(jié)果了。
接下來我們看下三類異步編程的實現(xiàn)。

三、三類異步編程實現(xiàn) 3.1 回調(diào)函數(shù)

demo1:

// 一個簡單的封裝
function want() {
    console.log("這是你想要執(zhí)行的代碼");
}

function fn(want) {
    console.log("這里表示執(zhí)行了一大堆各種代碼");

    // 其他代碼執(zhí)行完畢,最后執(zhí)行回調(diào)函數(shù)
    want && want();
}

fn(want);

demo2:

//callback hell

doSomethingAsync1(function(){
    doSomethingAsync2(function(){
        doSomethingAsync3(function(){
            doSomethingAsync4(function(){
                doSomethingAsync5(function(){
                    // code...
                });
            });
        });
    });
});

可以發(fā)現(xiàn)一個問題,在回調(diào)函數(shù)嵌套層數(shù)不深的情況下,代碼還算容易理解和維護(hù),一旦嵌套層數(shù)加深,就會出現(xiàn)“回調(diào)金字塔”的問題,就像demo2那樣,如果這里面的每個回調(diào)函數(shù)中又包含了很多業(yè)務(wù)邏輯的話,整個代碼塊就會變得非常復(fù)雜。從邏輯正確性的角度來說,上面這幾種回調(diào)函數(shù)的寫法沒有任何問題,但是隨著業(yè)務(wù)邏輯的增加和趨于復(fù)雜,這種寫法的缺點馬上就會暴露出來,想要維護(hù)它們實在是太痛苦了,這就是“回調(diào)地獄(callback hell)”。

回調(diào)函數(shù)還有一個問題就是我們在回調(diào)函數(shù)之外無法捕獲到回調(diào)函數(shù)中的異常,一般我們用try catch來捕捉異常,我們嘗試下捕捉回調(diào)中的異常


可以看到,不能捕捉到callback中的異常。

3.2 事件監(jiān)聽(事件發(fā)布/訂閱)

事件監(jiān)聽是一種非常常見的異步編程模式,它是一種典型的邏輯分離方式,對代碼解耦很有用處。通常情況下,我們需要考慮哪些部分是不變的,哪些是容易變化的,把不變的部分封裝在組件內(nèi)部,供外部調(diào)用,需要自定義的部分暴露在外部處理。從某種意義上說,事件的設(shè)計就是組件的接口設(shè)計。
1)jQuery事件監(jiān)聽

    $("#btn").on("myEvent", function(e) {
        console.log("There is my Event");
    });
    $("#btn").trigger("myEvent");

2)發(fā)布/訂閱模式

    var PubSub = function(){
        this.handlers = {}; 
    };
    PubSub.prototype.subscribe = function(eventType, handler) {
        if (!(eventType in this.handlers)) {
            this.handlers[eventType] = [];
        }
        this.handlers[eventType].push(handler); //添加事件監(jiān)聽器
        return this;//返回上下文環(huán)境以實現(xiàn)鏈?zhǔn)秸{(diào)用
    };
    PubSub.prototype.publish = function(eventType) {
        var _args = Array.prototype.slice.call(arguments, 1);
        for (var i = 0, _handlers = this.handlers[eventType]; i < _handlers.length; i++) {
            _handlers[i].apply(this, _args);//遍歷事件監(jiān)聽器
        }
        return this;
    };
    var event = new PubSub;//構(gòu)造PubSub實例
    event.subscribe("list", function(msg) {
        console.log(msg);
    });
    event.publish("list", {data: ["one,", "two"]});
    //Object {data: Array[2]}

這種模式實現(xiàn)的異步編程,本質(zhì)上還是通過回調(diào)函數(shù)實現(xiàn)的,所以3.1中提到的回調(diào)嵌套和無法捕捉異常的問題還是存在的,接下來我們看ES6提供的Promise對象,是否解決這兩個問題。

3.3 Promise對象

ES 6中原生提供了Promise對象,Promise對象代表了某個未來才會知道結(jié)果的事件(一般是一個異步操作),并且這個事件對外提供了統(tǒng)一的API,可供進(jìn)一步處理。
使用Promise對象可以用同步操作的流程寫法來表達(dá)異步操作,避免了層層嵌套的異步回調(diào),代碼也更加清晰易懂,方便維護(hù),也可以捕捉異常。

一個簡單例子:

function fn(num) {
  return new Promise(function(resolve, reject) {
    if (typeof num == "number") {
      resolve();
    } else {
      reject();
    }
  })
  .then(function() {
    console.log("參數(shù)是一個number值");
  })
  .then(null, function() {
    console.log("參數(shù)不是一個number值");
  })
}
fn("haha");
fn(1234);

為什么Promise 可以這樣實現(xiàn)異步編程,在這我們簡單分析下Promise實現(xiàn)過程:
1)極簡Promise雛形

// 極簡promise雛形
function Promise(fn) {
  var value = null,
    callbacks = [];  //callbacks為數(shù)組,因為可能同時有很多個回調(diào)

  this.then = function (onFulfilled) {
    callbacks.push(onFulfilled);
  };

  function resolve(value) {
    callbacks.forEach(function (callback) {
      callback(value);
    });
  }

  fn(resolve);
}

如果promise內(nèi)部的函數(shù)是同步函數(shù),我們要加入一些處理,保證在resolve執(zhí)行之前,then方法已經(jīng)注冊完所有的回調(diào);

通過setTimeout機(jī)制,將resolve中執(zhí)行回調(diào)的邏輯放置到JS任務(wù)隊列末尾,以保證在resolve執(zhí)行時,then方法的回調(diào)函數(shù)已經(jīng)注冊完成.

2)加入延時處理

// 極簡promise雛形,加入延時處理
function Promise(fn) {
  var value = null,
    callbacks = [];  //callbacks為數(shù)組,因為可能同時有很多個回調(diào)

  this.then = function (onFulfilled) {
    callbacks.push(onFulfilled);
  };

  function resolve(value) {
    setTimeout(function() {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }, 0)
  }

  fn(resolve);
}

如果Promise異步操作已經(jīng)成功,這時,在異步操作成功之前注冊的回調(diào)都會執(zhí)行,但是在Promise異步操作成功這之后調(diào)用的then注冊的回調(diào)就再也不會執(zhí)行了,這顯然不是我們想要的

3)加入狀態(tài)判斷

// 極簡promise雛形,加狀態(tài)判斷
function Promise(fn) {
  var state = "pending",
      value = null,
      callbacks = [];

  this.then = function (onFulfilled) {
      if (state === "pending") {
          callbacks.push(onFulfilled);
          return this;
      }
      onFulfilled(value);
      return this;
  };

  function resolve(newValue) {
      value = newValue;
      state = "fulfilled";
      setTimeout(function () {
          callbacks.forEach(function (callback) {
              callback(value);
          });
      }, 0);
  }

  fn(resolve);
}

4)鏈?zhǔn)絧romise

// 極簡promise雛形,鏈?zhǔn)絧romise
function Promise(fn) {
  var state = "pending",
      value = null,
      callbacks = [];

  this.then = function (onFulfilled) {
      return new Promise(function (resolve) {
          handle({
              onFulfilled: onFulfilled || null,
              resolve: resolve
          });
      });
  };

  function handle(callback) {
      if (state === "pending") {
          callbacks.push(callback);
          return;
      }
      //如果then中沒有傳遞任何東西
      if(!callback.onResolved) {
          callback.resolve(value);
          return;
      }

      var ret = callback.onFulfilled(value);
      callback.resolve(ret);
  }
  
  function resolve(newValue) {
      if (newValue && (typeof newValue === "object" || typeof newValue === "function")) {
          var then = newValue.then;
          if (typeof then === "function") {
              then.call(newValue, resolve);
              return;
          }
      }
      state = "fulfilled";
      value = newValue;
      setTimeout(function () {
          callbacks.forEach(function (callback) {
              handle(callback);
          });
      }, 0);
  }

  fn(resolve);
}
四、四個擴(kuò)展點 4.1 Promise常用的應(yīng)用場景:ajax

利用Promise的知識,對ajax進(jìn)行一個簡單的封裝??纯磿鞘裁礃幼樱?/p>

//demo3 promise封裝ajax
var url = "https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10";
function getJSON(url) {
  return new Promise(function(resolve, reject) {
    var XHR = new XMLHttpRequest();
    XHR.open("GET", url, true);
    XHR.send();

    XHR.onreadystatechange = function() {
        if (XHR.readyState == 4) {
            if (XHR.status == 200) {
                try {
                    var response = JSON.parse(XHR.responseText);
                    resolve(response);
                } catch (e) {
                    reject(e);
                }
            } else {
                reject(new Error(XHR.statusText));
            }
        }
    }
  })
}
getJSON(url).then(resp => console.log(resp));

除了串行執(zhí)行若干異步任務(wù)外,Promise還可以并行執(zhí)行異步任務(wù)。

當(dāng)有一個ajax請求,它的參數(shù)需要另外2個甚至更多請求都有返回結(jié)果之后才能確定,那么這個時候,就需要用到Promise.all來幫助我們應(yīng)對這個場景。

4.2 Promise.all

Promise.all接收一個Promise對象組成的數(shù)組作為參數(shù),當(dāng)這個數(shù)組所有的Promise對象狀態(tài)都變成resolved或者rejected的時候,它才會去調(diào)用then方法。

// demo4 promise.all
var url = "https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10";
var url1 = "https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-03-26/2017-06-10";

function renderAll() {
  return Promise.all([getJSON(url), getJSON(url1)]);
}

renderAll().then(function(value) {
  console.log(value); //將得到一個數(shù)組,里面是兩個接口返回的值
})

結(jié)果:

有些時候,多個異步任務(wù)是為了容錯。比如,同時向兩個URL讀取用戶的個人信息,只需要獲得先返回的結(jié)果即可。這種情況下,用Promise.race()實現(xiàn)。

4.3 Promise.race

與Promise.all相似的是,Promise.race都是以一個Promise對象組成的數(shù)組作為參數(shù),不同的是,只要當(dāng)數(shù)組中的其中一個Promsie狀態(tài)變成resolved或者rejected時,就可以調(diào)用.then方法了

// demo5 promise.race
function renderRace() {
  return Promise.race([getJSON(url), getJSON(url1)]);
}

renderRace().then(function(value) {
  console.log(value);
})

這里then()傳的value值將是接口返回比較快的接口數(shù)據(jù),另外一個接口仍在繼續(xù)執(zhí)行,但執(zhí)行結(jié)果將被丟棄。

結(jié)果:

4.4 Generator 函數(shù)

Generator函數(shù)是協(xié)程在ES 6中的實現(xiàn),最大特點就是可以交出函數(shù)的執(zhí)行權(quán)(暫停執(zhí)行)。
注意:在node中需要開啟--harmony選項來啟用Generator函數(shù)。
整個Generator函數(shù)就是一個封裝的異步任務(wù),或者說是異步任務(wù)的容器。異步操作需要暫停的地方,都用yield語句注明。

看個簡單的例子:

function* gen(x){
    var y = yield x + 2;
    return y;
}

var g = gen(1);
var r1 = g.next(); // { value: 3, done: false }
console.log(r1);
var r2 = g.next() // { value: undefined, done: true }
console.log(r2);

需要注意的是Generator函數(shù)的函數(shù)名前面有一個"*"。
上述代碼中,調(diào)用Generator函數(shù),會返回一個內(nèi)部指針(即遍歷器)g,這是Generator函數(shù)和一般函數(shù)不同的地方,調(diào)用它不會返回結(jié)果,而是一個指針對象。調(diào)用指針g的next方法,會移動內(nèi)部指針,指向第一個遇到的yield語句,上例就是執(zhí)行到x+2為止。
換言之,next方法的作用是分階段執(zhí)行Generator函數(shù)。每次調(diào)用next方法,會返回一個對象,表示當(dāng)前階段的信息(value屬性和done屬性)。value屬性是yield語句后面表達(dá)式的值,表示當(dāng)前階段的值;done屬性是一個布爾值,表示Generator函數(shù)是否執(zhí)行完畢,即是否還有下一個階段。

對Generator函數(shù),只有一個感性認(rèn)知,沒有實踐過,所以就先介紹到這了,后面還有ES7新的知識點async await,看了下網(wǎng)上的資料,理解得還不夠,希望后面自己接觸得更多再來這里補(bǔ)上,未完待續(xù)...

參考資料:
1) http://www.jianshu.com/p/12b9...
2) http://www.jianshu.com/p/fe5f...
3) https://mengera88.github.io/2...
4) http://www.cnblogs.com/nullcc...

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84905.html

相關(guān)文章

  • 前端_JavaScript

    摘要:為此決定自研一個富文本編輯器。例如當(dāng)要轉(zhuǎn)化的對象有環(huán)存在時子節(jié)點屬性賦值了父節(jié)點的引用,為了關(guān)于函數(shù)式編程的思考作者李英杰,美團(tuán)金融前端團(tuán)隊成員。只有正確使用作用域,才能使用優(yōu)秀的設(shè)計模式,幫助你規(guī)避副作用。 JavaScript 專題之惰性函數(shù) JavaScript 專題系列第十五篇,講解惰性函數(shù) 需求 我們現(xiàn)在需要寫一個 foo 函數(shù),這個函數(shù)返回首次調(diào)用時的 Date 對象,注意...

    Benedict Evans 評論0 收藏0
  • H5學(xué)習(xí)

    摘要:為此決定自研一個富文本編輯器。本文,主要介紹如何實現(xiàn)富文本編輯器,和解決一些不同瀏覽器和設(shè)備之間的。 對ES6Generator函數(shù)的理解 Generator 函數(shù)是 ES6 提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同。 JavaScript 設(shè)計模式 ② 巧用工廠模式和創(chuàng)建者模式 我為什么把他們兩個放在一起講?我覺得這兩個設(shè)計模式有相似之處,有時候會一個設(shè)計模式不能滿...

    aristark 評論0 收藏0
  • JavaScript 異步

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。寫一個符合規(guī)范并可配合使用的寫一個符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個需求:在系統(tǒng)初始化時通過http獲取一個第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個接口,可通過...

    tuniutech 評論0 收藏0
  • 前端知識點整理

    摘要:難怪超過三分之一的開發(fā)人員工作需要一些知識。但是隨著行業(yè)的飽和,初中級前端就業(yè)形勢不容樂觀。整個系列的文章大概有篇左右,從我是如何成為一個前端工程師,到各種前端框架的知識。 為什么 call 比 apply 快? 這是一個非常有意思的問題。 作者會在參數(shù)為3個(包含3)以內(nèi)時,優(yōu)先使用 call 方法進(jìn)行事件的處理。而當(dāng)參數(shù)過多(多余3個)時,才考慮使用 apply 方法。 這個的原因...

    Lowky 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<