[譯] 編寫函式式的 JavaScript 實用指南

駕考寶典heyushuo發表於2019-01-22
[譯] 編寫函式式的 JavaScript 實用指南

一切皆為函式

函數語言程式設計很棒。隨著 React 的引入,越來越多的 JavaScript 前端程式碼正在考慮 FP 原則。但是我們如何在我們編寫的日常程式碼中開始使用 FP 思維模式?我將嘗試使用日常程式碼塊並逐步重構它。

我們的問題:使用者來到我們的登入頁面連結後會帶一個redirect_to 引數。就像/login?redirect_to =%2Fmy-page。請注意,當%2Fmy-page 被編碼為 URL 的一部分時,它實際上是/ my-page。我們需要提取此引數,並將其儲存在本地儲存中,以便在完成登入後,可以將使用者重定向到 my-page頁面。

第 0 步:必要的方法

如果我們以最簡單方式來呈現這個解決方案,我們將如何編寫它?我們需要如下幾個步驟

  1. 解析連結後引數。
  2. 獲取 redirect_to 值。
  3. 解碼該值。
  4. 將解碼後的值儲存在 localStorage 中。

我們還必須將不安全的函式放到try catch塊中。有了這些,我們的程式碼將如下所示:

function persistRedirectToParam() { 
let parsedQueryParam;
try {
//獲取連線後的引數{redirect_to:'/my-page'
}
parsedQueryParam = qs.parse(window.location.search);
// https://www.npmjs.com/package/qs
} catch (e) {
console.log(e);
return null;

} //獲取到引數 const redirectToParam = parsedQueryParam.redirect_to;
if (redirectToParam) {
const decodedPath = decodeURIComponent(redirectToParam);
try {
localStorage.setItem('REDIRECT_TO', decodedPath);

} catch (e) {
console.log(e);
return null;

} //返回 my-page return decodedPath;

} return null;

}複製程式碼

第 1 步:將每一步寫為函式

暫時,讓我們忘記 try catch 塊並嘗試將所有內容表達為函式。

// // 讓我們宣告所有我們需要的函式const parseQueryParams = query =>
qs.parse(query);
const getRedirectToParam = parsedQuery =>
parsedQuery.redirect_to;
const decodeString = string =>
decodeURIComponent(string);
const storeRedirectToQuery = redirectTo =>
localStorage.setItem('REDIRECT_TO', redirectTo);
function persistRedirectToParam() {
//使用它們 const parsed = parseQueryParams(window.location.search);
const redirectTo = getRedirectToParam(parsed);
const decoded = decodeString(redirectTo);
storeRedirectToQuery(decoded);
return decoded;

}複製程式碼

當我們開始將所有“結果”用函式的方式表示時,我們會看到我們可以從主函式體中重構的內容。這樣處理後,我們的函式變得更容易理解,並且更容易測試。

早些時候,我們將測試主要函式作為一個整體。但是現在,我們有 4 個較小的函式,其中一些只是代理其他函式,因此需要測試的足跡要小得多。

讓我們識別這些代理函式,並刪除代理,這樣我們就可以減少一些程式碼。

const getRedirectToParam = parsedQuery =>
parsedQuery.redirect_to;
const storeRedirectToQuery = redirectTo =>
localStorage.setItem('REDIRECT_TO', redirectTo);
function persistRedirectToParam() {
const parsed = qs.parse(window.location.search);
const redirectTo = getRedirectToParam(parsed);
const decoded = decodeURIComponent(redirectTo);
storeRedirectToQuery(decoded);
return decoded;

}複製程式碼

第 2 步 嘗試編寫函式式

好的。現在,似乎 persistRedirectToParam 函式是 4 個其他函式的“組合”讓我們看看我們是否可以將此函式編寫為合成,從而消除我們儲存為 const 的中間結果。

const getRedirectToParam = (parsedQuery) =>
parsedQuery.redirect_to;
// we have to re-write this a bit to return a result.const storeRedirectToQuery = (redirectTo) =>
{
localStorage.setItem("REDIRECT_TO", redirectTo) return redirectTo;

};
function persistRedirectToParam() {
const decoded = storeRedirectToQuery( decodeURIComponent( getRedirectToParam( qs.parse(window.location.search) ) ) ) return decoded;

}複製程式碼

這很好。但是我同情讀取這個巢狀函式呼叫的人。如果有辦法解開這個混亂,那就太棒了。

第 3 步 更具可讀性的組合

如果你已經完成了以上的一些重構,那麼你就會遇到composeCompose 是一個實用函式,它接受多個函式,並返回一個逐個呼叫底層函式的函式。還有其他很好的資源來學習 composition,所以我不會在這裡詳細介紹。

使用 compose,我們的程式碼將如下所示:

const compose = require('lodash/fp/compose');
const qs = require('qs');
const getRedirectToParam = parsedQuery =>
parsedQuery.redirect_to;
const storeRedirectToQuery = redirectTo =>
{
localStorage.setItem('REDIRECT_TO', redirectTo);
return redirectTo;

};
function persistRedirectToParam() {
const op = compose( storeRedirectToQuery, decodeURIComponent, getRedirectToParam, qs.parse );
return op(window.location.search);

}複製程式碼

compose 內的函式執行順序為從右向左,即最右邊的函式(最後一個引數)最先執行,執行完的結果作為引數傳遞給前一個函式。因此,在 compose 鏈中呼叫的第一個函式是最後一個函式。

如果你是一名數學家並且熟悉這個概念,這對你來說不是一個問題,所以你自然會從右到左閱讀。但對於熟悉命令式程式碼的其他人來說,我們想從左到右閱讀。

第 4 步 pipe(管道)和扁平化

幸運的是這裡有pipe(管道)compose 做了同樣的事情,但是執行順序和 compose 是相反的,因此鏈中的第一個函式最先執行,執行完的結果作為引數傳遞給下一個函式。

而且,似乎我們的 persistRedirectToParams 函式已經成為另一個我們稱之為 op 的函式的包裝器。換句話說,它所做的只是執行op。我們可以擺脫包裝並“扁平化”我們的函式。

const pipe = require('lodash/fp/pipe');
const qs = require('qs');
const getRedirectToParam = parsedQuery =>
parsedQuery.redirect_to;
const storeRedirectToQuery = redirectTo =>
{
localStorage.setItem('REDIRECT_TO', redirectTo);
return redirectTo;

};
const persistRedirectToParam = fp.pipe( qs.parse, getRedirectToParam, decodeURIComponent, storeRedirectToQuery);
複製程式碼

差不多了。請記住,我們適當地將 try-catch 塊留在後面,以使其達到正確的狀態?好的接下來,我們需要一些方式來介紹它。qs.parse 和 storeRedirectToQuery 都是不安全。一種選擇是使它們成為包裝函式並將它們放在 try-catch 塊中。另一種函式式方式是將 try-catch 表示為一種函式。

第 5 步 作為函式的異常處理

有一些實用程式做到了這一點,但讓我們自己嘗試寫一些東西。

function tryCatch(opts) { 
return args =>
{
try {
return opts.tryer(args);

} catch (e) {
return opts.catcher(args, e);

}
};

}複製程式碼

我們的函式在這裡需要一個包含 tryer 和 catcher 函式的 opts 物件。它將返回一個函式,當使用引數呼叫時,使用所述引數呼叫 tryer 並在失敗時呼叫 catcher。現在,當我們有不安全的操作時,我們可以將它們放入 tryer 部分,如果它們失敗,則從捕獲器部分進行救援並提供安全結果(甚至記錄錯誤)。

第 6 步 把所有東西放在一起

因此,考慮到這一點,我們的最終程式碼如下:

const pipe = require('lodash/fp/pipe');
const qs = require('qs');
const getRedirectToParam = parsedQuery =>
parsedQuery.redirect_to;
const storeRedirectToQuery = redirectTo =>
{
localStorage.setItem('REDIRECT_TO', redirectTo);
return redirectTo;

};
const persistRedirectToParam = fp.pipe( tryCatch({
tryer: qs.parse, catcher: () =>
{
return {
redirect_to: null // we should always give back a consistent result to the subsequent function
};

}
}), getRedirectToParam, decodeURIComponent, tryCatch({
tryer: storeRedirectToQuery, catcher: () =>
null // if localstorage fails, we get null back
}));
// to invoke, persistRedirectToParam(window.location.search);
複製程式碼

這或多或少是我們想要的。但是為了確保程式碼的可讀性和可測試性得到改善,我們也可以將“安全”函式(tryCatch 函式)分解出來。

const pipe = require('lodash/fp/pipe');
const qs = require('qs');
const getRedirectToParam = parsedQuery =>
parsedQuery.redirect_to;
const storeRedirectToQuery = redirectTo =>
{
localStorage.setItem('REDIRECT_TO', redirectTo);
return redirectTo;

};
const safeParse = tryCatch({
tryer: qs.parse, catcher: () =>
{
return {
redirect_to: null // we should always give back a consistent result to the subsequent function
};

}
});
const safeStore = tryCatch({
tryer: storeRedirectToQuery, catcher: () =>
null // if localstorage fails, we get null back
});
const persistRedirectToParam = fp.pipe( safeParse, getRedirectToParam, decodeURIComponent, safeStore);
複製程式碼

現在,我們得到的是一個更強大功能的函式,由 4 個獨立的函式組成,這些函式具有高度內聚性,鬆散耦合,可以獨立測試,可以獨立重用,考慮異常場景,並且具有高度宣告性。

有一些 FP 語法糖使這變得更好,但是這是以後的某一天。

如果發現譯文存在錯誤或其他需要改進的地方請指出。

來源:https://juejin.im/post/5c46c4416fb9a049eb3c42d5

相關文章