Promise 源碼解析—— .then 處理小細節

今天看到一段callback代碼

var dataCache = null;

function getAjaxData(callback){
  if ( dataCache ) {
    callback && callback(dataCache);
  } else {
    $.ajax({
     .....
    })
   .done( (res) => {
       dataCache = res;
       callback && callback(res);
   })
  }
}

上面是一個callback 寫的ajax 調用。 設置了一個緩存, 在重復調用函數的時候會取緩存。

這時候調用這個函數會有一個問題。

getAjaxData( () => {
  console.log(2);
})
console.log(1);

上面的代碼我們會有 先輸出1 后輸出 2 的 預期。
但實際上在第二次執行該方法會命中緩存,然后先輸出 2 后輸出 1。 于是導致了BUG。

把callback代碼優化為 promise 方式。

var dataCache = null;

function getAjaxData(){
  return new Promise( (resolve, rej) => {

  if ( dataCache ) {
    resolve();
  } else {
    $.ajax({
     .....
    })
   .done( () => {
       resolve();
   })
  }

  })
  
}

這個時候就不管調用多少次都是異步了 OK, 背景介紹完畢。

那我們進入正題,來看看Promise是如何實現的吧。

var asap = require('asap/raw');
...
...
Promise.prototype.then = function(onFulfilled, onRejected) {
  if (this.constructor !== Promise) {
    return safeThen(this, onFulfilled, onRejected);
  }
  var res = new Promise(noop);
  handle(this, new Handler(onFulfilled, onRejected, res));
  return res;
};

function handle(self, deferred) {

  while (self._state === 3) {
    self = self._value;
  }
  if (Promise._onHandle) {
    Promise._onHandle(self);
  }
  if (self._state === 0) {
    if (self._deferredState === 0) {
      self._deferredState = 1;
      self._deferreds = deferred;
      return;
    }
    if (self._deferredState === 1) {
      self._deferredState = 2;
      self._deferreds = [self._deferreds, deferred];
      return;
    }
    self._deferreds.push(deferred);
    return;
  }
  handleResolved(self, deferred);
}

function handleResolved(self, deferred) {
  asap(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {
      if (self._state === 1) {
        resolve(deferred.promise, self._value);
      } else {
        reject(deferred.promise, self._value);
      }
      return;
    }
    var ret = tryCallOne(cb, self._value);
    if (ret === IS_ERROR) {
      reject(deferred.promise, LAST_ERROR);
    } else {
      resolve(deferred.promise, ret);
    }
  });
}

全局搜了 setTimeout 發現沒有。 那怎么做到強行異步的呢, 唯一可能的地方就是這里用到的 asap 這個庫了。
讓我們看看

asap.js

var rawAsap = require("./raw");
var freeTasks = [];

/**
 * Calls a task as soon as possible after returning, in its own event, with
 * priority over IO events. An exception thrown in a task can be handled by
 * `process.on("uncaughtException") or `domain.on("error")`, but will otherwise
 * crash the process. If the error is handled, all subsequent tasks will
 * resume.
 *
 * @param {{call}} task A callable object, typically a function that takes no
 * arguments.
 */

Calls a task as soon as possible after returning。

we get it!

這個庫的實現源碼如下, 值得一看
https://github.com/kriskowal/asap/blob/master/browser-raw.js

setTimeout(function(){
  console.log(2);
}, 0);
var observer = new MutationObserver(function(){
  console.log(1);
});
var node = document.createTextNode("");
observer.observe(node, {characterData: true});
node.data = 1;

console.log(3);

上面代碼的輸出是什么呢?^?_?^

歡迎吐槽筆者自己寫的 proimse。
https://github.com/KMBaby-zyl/tiny-co/blob/master/promise/promise.js

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容