js中非同步處理

sundjly發表於2019-04-01

github的地址 歡迎star!
俗話說非同步是萬惡之源,但是沒有非同步是萬萬不行的

問題引出:

  • 在react裡面的一個父元件中通過發起請求獲取後臺的資料,然後傳遞給子元件,發現在子元件用到該資料時並沒有觸發相應的改變。開啟谷歌控制檯發現xhr請求還在pending。因此就總結了非同步的處理。

js非同步的幾種方式

1.回撥

回撥函式的優點是簡單、容易理解和部署,缺點是不利於程式碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程會很混亂,而且每個任務只能指定一個回撥函式。

2.事件監聽

參考阮老師的解釋

3. 釋出/訂閱

來自於--JavaScript設計模式與開發實踐

const event = {

  clientList: [],

  listen(key, fn) {
    if (!this.clientList[key]) {
      this.clientList[key] = [];
    }
    this.clientList[key].push(fn); // 訂閱的訊息新增進快取列表
  },

  trigger() {
    let key = Array.prototype.shift.call(arguments);
    let fns = this.clientList[key];

    if (!fns || fns.length === 0) {
      return false;
    }
    for (let i = 0, fn; fn = fns[i++];) {
      fn.apply(this, arguments);
      //arguments 是trigger帶上的引數
    }
  },

  //  移除事件
  remove(key, fn) {
    let fns = this.clientList[key];
    if(!fns) return false;
    if(!fn){
      fns && (fns.length = 0);
    }else{
      for(let i = fns.length -1; i>=0; i--){
        let _fn = fns[i];
        if(_fn == fn){
          fns.splice(i,1); //刪除訂閱者的回撥函式
        }
      }
    }
  }
};
複製程式碼

上面在使用的時候還沒有解決命名衝突的問題。具體使用的程式碼應該充分考慮

// 解決命名衝突問題
var globalEvent = (function () {
  var global = this, Event,
    _default = 'default';

  Event = function () {
    var _listen,
      _trigger,
      _remove,
      _slice = Array.prototype.slice,
      _shift = Array.prototype.shift,
      _unshift = Array.prototype.unshift,
      namespaceCache = {},
      _create,
      find,
      each = function (ary, fn) {
        var ret;
        for (var i = 0, l = ary.length; i < l; i++) {
          var n = ary[i];
          ret = fn.call(n, i, n);
        }
        return ret;
      };

    _listen = function (key, fn, cache) {
      if (!cache[key]) {
        cache[key] = [];
      }
      cache[key].push(fn);
    };

    _remove = function (key, cache, fn) {
      if (cache[key]) {
        if (fn) {
          for (var i = cache[key].length; i >= 0; i--) {
            if (cache[key][i] === fn) {
              cache[key].splice(i, 1);
            }
          }
        } else {
          cache[key] = [];
        }
      }
    };

    _trigger = function () {
      var cache = _shift.call(arguments),
        key = _shift.call(arguments),
        args = arguments,
        _self = this,
        ret,
        stack = cache[key];

      if (!stack || !stack.length) {
        return;
      }

      return each(stack, function () {
        return this.apply(_self, args);
      });
    };

    _create = function (namespace) {
      var namespace = namespace || _default;
      var cache = {},
        offlineStack = [], //離線事件
        ret = {
          listen: function (key, fn, last) {
            _listen(key, fn, cache);
            if (offlineStack === null) {
              return;
            }
            if (last === 'last') {
              offlineStack.length && offlineStack.pop()();
            } else {
              each(offlineStack, function () {
                this();
              });
            }
            offlineStack = null;

          },
          one: function (key, fn, last) {
            _remove(key, cache);
            this.listen(key, fn, last);
          },
          remove: function (key, fn) {
            _remove(key, cache, fn);
          },
          trigger: function () {
            var fn,
              args,
              _self = this;

            _unshift.call(arguments, cache);
            args = arguments;
            fn = function () {
              return _trigger.apply(_self, args);
            };

            if (offlineStack) {
              return offlineStack.push(fn);
            }
            return fn();
          }
        };
      return namespace ? (namespaceCache[namespace] ?
          namespaceCache[namespace] : namespaceCache[namespace] = ret
      ) : ret;
    };

    return {
      create: _create,
      one: function (key, fn, last) {
        var event = this.create();
        event.one(key, fn, last);
      },
      remove: function (key, fn) {
        var event = this.create();
        event.remove(key, fn);
      },
      listen: function (key, fn, last) {
        var event = this.create();
        event.listen(key, fn, last);
      },
      trigger: function () {
        var event = this.create();
        event.trigger.apply(this, arguments);
      }
    };
  }();
  return Event;

})();
// 運用了虛擬代理,
複製程式碼

此時,可能就會問到,釋出訂閱和觀察者模式的區別:引入知乎裡面的回答:

釋出訂閱模式屬於廣義上的觀察者模式

釋出訂閱模式是最常用的一種觀察者模式的實現,並且從解耦和重用角度來看,更優於典型的觀察者模式

釋出訂閱模式多了個事件通道

js中非同步處理

  1. 觀察者模式中,觀察者需要直接訂閱目標事件;在目標發出內容改變的事件後,直接接收事件並作出響應
  2. 釋出訂閱模式中,釋出者和訂閱者之間多了一個釋出通道;一方面從釋出者接收事件,另一方面向訂閱者釋出事件;訂閱者需要從事件通道訂閱事件

具體的總結:

  1. 在觀察者中,觀察者監聽著目標物件,目標物件也管理儲存著它的觀察者們,釋出訂閱中,釋出者和訂閱者之間是獨立(不需要互相認識),它們只在訊息佇列或代理情況才能通訊
  2. 在釋出訂閱模式中,元件是鬆散連線的,觀察者與之相反
  3. 觀察者模式執行大多是通過同步的方法,例如,當一些事件觸發時,目標物件(Subject)就會呼叫觀察者的方法。釋出訂閱模式執行大多是非同步的(使用訊息佇列)
  4. 觀察者模式需要在單個應用程式地址空間上實現,相對應的,釋出-訂閱更像交叉應用模式。

觀察者模式像rxjs,可以看一下 rxjs 原理解析

4.Promise/Async

每一個非同步任務返回一個Promise物件,該物件有一個then方法,允許指定回撥函式。 在promise裡面主要作用就是將毀掉巢狀書寫為鏈式呼叫的方式,其中可以給then方法傳遞函式,將多個依賴非同步拆分,(方便維護,可擴充套件性)。對於需要同時得到結果的多個非同步,就可以使用Promise.all的方法

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!

參考:

  1. www.ruanyifeng.com/blog/2016/0…
  2. zhuanlan.zhihu.com/p/32911022
  3. facebook.github.io/react
  4. www.jackcallister.com/2015/01/05/…
  5. ryanclark.me/getting-sta…
  6. www.zhihu.com/question/23…
  7. hackernoon.com/observer-vs…

相關文章