min.

搞懂每天陪伴我們的 JavaScript Promise

2021-02-03# javascript

Promise 緣起

Promise 在程式語言裡面不是新概念3,但 JavaScript 裡面的實作原先通行使用的解決方案是 2011 年 jQuery 的 Defered Object 4,接著2012 年在 ECMAScript 提案出現 Promise/A+ 的 Promise Spec 5 最後才在 2015 年正式成為標準。

這也是為什麼提到 JavaScript Promise 最常見的就是 Promise/A+ 版本1,而 ES6 Promise 的實現是完整符合 Promise/A+ 但兩者並不相同2,可以把 Promise/A+ 的規範想像成最基本版的 Promise 規格,因為 ES6 Promise 又延伸了更多功能。

而之所以有這個提案出現是因為在 JavaScript 中原本做非同步處理是透過 Callback Function 來做處理,但是 Callback Function 很容易形成 Callback Hell 的程式碼,導致很難維護,所以才使用 CS 中早有的這個概念來處理非同步問題,同樣可以解決非同步問題的策略還有 CAF 6

Promise 規範

在 Promise/A+ 規範裡面規定 Promise 要有下列這些元素,以下簡單翻譯規範內容:

  • Promise: 一個 Object / Function 可以透過 then 方法來符合這個規範
  • Thenable: 一個 Object / Function 定義 then 方法
  • Value: 一個合法的 JavaScript 值
  • Exception: 一個在 Throw 狀態被丟出來的 Value
  • Reason: 一個 Value 表示 Promise 為什麼被拒絕。

而規範上的 Promise 有三種狀態

  • Pending: 可以變成 Fulfilled 或 Rejected 其中一種狀態
  • Fulfilled: 為最終狀態、一定會有一個不變的 value
  • Rejected: 為最終狀態、一定會有一個不變的 reason

這三種狀態之間的變化規則如下,Promise 透過一個 then 方法接收兩個 function 作為 optional 參數(如果不是 function 則無視) ,Fulfilled 狀態時會呼叫 onFulfilled 一次、Rejected 狀態會呼叫 onRejected 一次,呼叫後回傳 Promise 物件,來取得 value 跟 reason:

promise2 = promise.then(onFulfilled, onRejected)

Promise 實現11

平常使用 Promise 的方式

const delay = function() {
  return new Promise(function(resolve, reject){
   setTimeout(resolve, 1000);
  });
};

delay().then(() => {
  console.log(1);
})

Constructor

class TestPromise {
  constructor(executor) {
   this.state = 'pending';
   this.value = undefined;
   /*
   *	const c = sleep(10);
   * 	c.then (()=> console.log('10 second pass'))
   * 	c.then (()=> console.log('Hello,'))
   * 	c.then (()=> console.log('World!'))
   */
   this.callbacks = [];
   // resolve & reject 用來切換 state
   executor(
     value=>this.resolve(value),
     reason=>this.reject(reason)
   );
  }
}

Transition State

class TestPromise {
  constructor(executor) {
   ...
  }

   resolve(value){
     if(this.state!== 'pending') return;
     this.state = 'fulfilled';
     this.value = value;
     this.run()
   }

    reject(reason){
      if(this.state!== 'pending') return;
      this.state = 'rejeceted';
      this.value = reason;
      this.run()
    }
}

Then Method

class TestPromise {
  constructor(executor) {
   ...
  }

  resolve(value){ ... }
  reject(reason){ ... }
  then(onFulfilled, onRejected) {
   // then 回傳的也會是一個 Promise
   let newPromise = new TestPromise(() => {});
   newPromise.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
   newPromise.onRejected = typeof onRejected === 'function' ? onRejected : null;
   this.callbacks.push(newPromise);
   this.run();
   return newPromise;
  }
}

Execution

class TestPromise {
  constructor(executor) {
   ...
  }

  resolve(value){ ... }
  reject(reason){ ... }
  then(onFulfilled, onRejected) { ... }

  // 負責根據狀態來執行對應的 function
  run() {
   let callbackName, resolver;
   if (this.state === 'pending') return;
   if (this.state === 'fulfilled') {
    callbackName = 'onFulfilled';
    resolver = 'resolve';
   }
   if (this.state === 'rejected') {
    callbackName = 'onRejected';
    resolver = 'reject';
   }
   // 由於是在 JavaScript 中實現,根據 2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1]. 所以我們必須綁一個 setTimeout 來確保 onFulfilled, OnRejected 可以在後續完成。
   setTimeout(() => {
     this.callbacks.forEach(cb => {
       try {
         let fn = cb[callbackName];
         if (fn) cb.resolve(fn(this.value));
         else cb[resolver](this.value);
       } catch (e) {
         cb.reject(e);
       }
       this.todos.shift();
     });
   })
  }
}

Promise

除了解決 Callback Hell 之外,Promise 設計上的其他好處8

  • 可靠性:Promise A+ 明確定義了 Promise resolve, reject function 的執行次數
  • 控制反轉:將 Callback 的執行
  • 底層由 Microtask Queue 去執行,優先序跟資源上更高

Async & Await 實現10

Generator & Iterator 7

ES6 的新語法,當 Function 屬於 Generator Function 的時候,會回傳一個 Generator Object 符合 Iterator 跟 Interable Protocal9,可以被 for ... of 處理、也可以使用 next() method。

function* Hello() {
    yield 100
    yield (function () {return 200})()
    return 300
}

var h = Hello()
console.log(typeof h)  // object

console.log(h.next())  // { value: 100, done: false }
console.log(h.next())  // { value: 200, done: false }
console.log(h.next())  // { value: 300, done: true }
console.log(h.next())  // { value: undefined, done: true }

Async Await 轉換成 Generator

如果現在是一個非同步希望今天可以跟據 Promise 狀態自動執行 next method 呢?

下面是一個 Async, Await 使用範例

const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))

async function test() {
  const data = await getData()
  console.log('data: ', data);
  const data2 = await getData()
  console.log('data2: ', data2);
  return 'success'
}

test().then(res => console.log(res))

將 test() 轉換成 Generator

function* testG() {
  const data = yield getData()
  console.log('data: ', data);
  const data2 = yield getData()
  console.log('data2: ', data2);
  return 'success'
}

// 手動執行 next() method
var gen = testG()
var dataPromise = gen.next()
dataPromise.then((value1) => {
    var data2Promise = gen.next(value1)
    console.log('data: ', data);

    data2Promise.value.then((value2) => {
        gen.next(value2)
        console.log('data2: ', data2);
    })
})

參考資料

# javascript

© 2020 minw Powered by Gatsby