點燈坊

失くすものさえない今が強くなるチャンスよ

使用 eventually() 處理 Fulfilled 與 Rejected Promise 共用邏輯

Sam Xiao's Avatar 2019-09-19

ECMAScript 2018 的 finally()try catch finally 的確可處理 Fulfilled Promise 與 Rejected Promise 共用邏輯,但可惜還不夠 Functional,透過 Wink-fp 的 eventually() 可使 Promise Chain 也能 Function Composition。

Version

macOS Mojave 10.14.6
VS Code 1.38.1
Quokka 1.0.248
ECMAScript 2018
Ramda 0.26.1
Wink-fp 0.0.11

finally()

let fetchData = () => Promise.resolve('Hello World');

fetchData().then(
  x => {
    console.log(x);
    console.log('Fetch completed');
  }
).catch(
  e => {
    console.log(e);
    console.log('Fetched completed');
  }
);

若無論是 fulfilled Promise 或 rejected Promise,最後我們都希望印出 Fetched completed

若只有 then()catch(),則 fulfilled handler 與 rejected handler 都必須包含 console.log('Fetched completed')

finally000

let fetchData = () => Promise.resolve('Hello World');

fetchData()
  .then(x => console.log(x))
  .catch(e => console.log(e))
  .finally(() => console.log('Fetched completed'));

ES2018 提供了 finally(),可將 fulfilled handler 與 rejected handler 重複部分重構到 finally() 的 callback 中。

finally001

Try Catch Finally

let fetchData = () => Promise.resolve('Hello World');

try {
  let x = await fetchData();
  console.log(x);
} catch(e) {
  console.log(e);
} finally {
  console.log('Fetched completed')
}

自從 ES2017 可將 await 用於 try catch block 中之後,自然也能將 finally() 寫在 finally block 內。

這種寫法個符合 imperative 與 synchronous 習慣。

finally002

eventually()

import { then, otherwise, pipe } from 'ramda';
import { resolve, log } from 'wink-fp';

let fetchData = () => resolve('Hello World');

let eventually = cb => ps => ps.finally(cb);

let fn = pipe(
  fetchData,
  then(log),
  otherwise(log),
  eventually(() => console.log('Fetched completed'))
);

fn();

我們也可自行建立 eventually()Promise.prototype.finally() 包成 function,如此就能使用 pipe() 以 pipeline 方式呈現 promise chain。

finally005

Wink-fp

import { then, otherwise, pipe, always } from 'ramda';
import { resolve, log, eventually } from 'wink-fp';

let fetchData = () => resolve('Hello World');

let onEventual = pipe(
  always('Fetched complete'),
  log
);

let fn = pipe(
  fetchData,
  then(log),
  otherwise(log),
  eventually(onEventual)
);

fn();

Wink-fp 已經提供 eventually(),可直接使用。

eventually()
(a -> b) -> (Promise e a) -> (Promise e b)
Promise.prototype.finally() 的 function 版本

(a -> b):synchronous function

Promise e a:data 為 promise

Promise e b:回傳亦為 promise

finally003

Function Composition

import { then, otherwise, compose, always } from 'ramda';
import { resolve, log, eventually } from 'wink-fp';

let fetchData = () => resolve('Hello World');

let onEventual = compose(
  log,
  always('Fetched comple')
);

let fn = compose(
  eventually(onEventual),
  otherwise(log),
  then(log),
  fetchData
);

fn();

既然能使用 pipe() 達成 pipeline,反向使用 compose() 就是 function composition 了。

finally004

Conclusion

  • finally() 能避免 then()catch() 中有重複邏輯
  • 也可使用 try catch finally block,繼續使用 imperative 思維
  • seventually()Promise.prototype.finally() 的 function 版本,因此可使用 function composition

Reference

MDN, Promise.prototype.finally()