Какво по дяволите е „Thunk“ и какво е „Currying“ в JavaScript?

Демистифициране на Thunks и Currying — някои от най-важните концепции за функционално програмиране в JavaScript!

Съвсем сигурен съм, че когато за първи път чухте термини като thunks или curry в програмирането, реакцията ви щеше да бъде нещо подобно:

Е, не се притеснявайте, аз се почувствах по същия начин, когато чух за тези термини за първи път, особено от гледна точка на програмирането, докато не разбрах колко са полезни!

Преди да започнем, тази публикация в блога предполага, че имате основни познания по JavaScript и асинхронно програмиране с помощта на обещания и обратни извиквания. Някои познания за функционалното програмиране в JavaScript също биха били много полезни за по-добро разбиране на двете концепции. Thunks, заедно с curry, са доста полезни концепции, когато става дума за функционално програмиране в JavaScript.

Нека се потопим по-дълбоко в тези концепции и да разберем какво представляват и как можем да ги използваме!

Всичко за thunks в JavaScript

Използваме обратни извиквания в JavaScript почти навсякъде за различни случаи на употреба като асинхронно програмиране, прослушване на събития, обработване на грешки и много повече. Thunks основно разширяват възможностите на обратните извиквания и са доста полезни, когато става въпрос за асинхронно програмиране в JavaScript, въпреки че все още са доста полезни в определени сценарии, включващи синхронно програмиране.

Какво е thunk?

Thunk е просто функция, която забавя изчисляването на стойност или някаква логика.

Думата „thunk“ е програмен термин, който означава „част от код, който върши някаква забавена работа“.

Вместо да изпълняваме някаква логика сега, можем да напишем тяло на функция или код, който може да се използва за извършване на работата по-късно. Много от вас, работещи с React, вероятно познават една страхотна и проста библиотека, наречена redux-thunk, която, както подсказва името, е базирана на прехвърляния и е полезна, когато правите управление на състоянието с помощта на Redux.

Има две гледни точки, в които можете да използвате thunks: синхронен и асинхронен.

Синхронен начин за използване на thunks

Нека да поговорим за това как можете да използвате синхронен thunk,

const power = (base, exponent) => {
  return Math.pow(base, exponent);
};

// `power` function is evaluated
// and the result is returned immediately
console.log(power(99, 9)); // 913517247483640800

// This is a thunk
const powerThunk = () => {
  return power(99, 9);
};

// Returns the same result as calling the `power` function
console.log(powerThunk()); // 913517247483640800

Горният кодов фрагмент power е проста функция, която изчислява степента на число и незабавно връща изчислената стойност. powerThunk е синхронно прехвърляне, което забавя този резултат, докато не го извикаме. Както може би сте забелязали, powerThunk използва функцията power вътрешно, за да изчисли и върне изчислената стойност.

Важната част е, че този тънк се е превърнал в обвивка около определено състояние. В този случай това е резултат от потенциално скъпа операция. Този модел ни позволява да скрием вътрешните детайли на изчислението, подобно на принципа на Енкапсулация на парадигмата на обектно-ориентираното програмиране.

Сега нека поговорим за това как можете да използвате thunks от гледна точка на асинхронното програмиране.

Асинхронен thunks

И така, как можем да опишем async thunk? Това е проста функция, която не се нуждае от допълнителни параметри, освен функция за обратно извикване. В случай на асинхронни прехвърляния, можем да въведем забавяне за обработка на синхронна функция като функцията power от предишния пример или да я използваме за обработка на асинхронни API извиквания.

Нека да разгледаме и двата случая. Можем да променим предишния пример за синхронен thunk, за да разберем това по-добре.

const powerAsync = (base, exponent, callback) => {
  return setTimeout(() => callback(Math.pow(base, exponent)), 1000);
};

// This is an async thunk
const powerAsyncThunk = (callback) => {
  return function () {
    powerAsync(99, 9, callback);
  };
};

// This async thunk now returns a function that 
// can be called later on to calculate power.
const calculatePower = powerAsyncThunk((result) => console.log(result));
calculatePower();

В горния кодов фрагмент модифицирахме функцията power на функция powerAsync, която фалшифицира асинхронна функция с помощта на setTimeout и приема допълнителен параметър callback. Сега нашият async thunk powerAsyncThunk забавя изпълнението на powerAsync функция, като връща функция. Това го прави наистина полезен, тъй като сега можем просто да извикаме тази функция, върната от powerAsyncThunk, когато пожелаем в нашия код по-късно.

Сега нека поговорим за това как можем да се справим с асинхронни извиквания на API с помощта на async thunks.

const fetchCurrenciesData = (callback) => {
  fetch("https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies.json")
  .then(res => res.json())
  .then(res => callback(res));
}

// This is an async thunk
const asyncThunk = (callback) => {
  return function () {
    fetchCurrenciesData(callback);
  }
}

// This async thunk now returns a function that 
// can be called later on to fetch data from the API.
const fetchCurrencies = asyncThunk((res) => console.log(res));
fetchCurrencies();

Функцията fetchCurrenciesData прави извикване на API за извличане на всички валути на различни държави и също така приема функция callback като параметър. Async thunk asyncThunk връща функция, която може да бъде извикана по-късно, за да извлече данните от API, когато пожелаем в нашия код.

Данните след извикването на функцията fetchCurrencies изглеждат по следния начин:

{
aave: "Aave"
ada: "Cardano"
aed: "United Arab Emirates Dirham"
afn: "Afghan afghani"
algo: "Algorand"
doge: "Dogecoin"
dop: "Dominican peso"
dot: "Dotcoin"
dzd: "Algerian dinar"
egld: "Elrond"
egp: "Egyptian pound"
enj: "Enjin Coin"
eos: "EOS"
ern: "Eritrean nakfa"
etb: "Ethiopian birr"
ils: "Israeli New Shekel"
imp: "CoinIMP"
inj: "Injective"
inr: "Indian rupee"
iqd: "Iraqi dinar"
irr: "Iranian rial"
isk: "Icelandic króna"
jep: "Jersey Pound"
jmd: "Jamaican dollar"
jod: "Jordanian dinar"
jpy: "Japanese yen"
kava: "Kava"
kcs: "Kucoin"
kda: "Kadena"
kes: "Kenyan shilling"
...
} 

Къде точно се използват thunks? 💡

Сега, след като научихте доста за thunks, може би се чудите къде точно се използват thunks.

  • Обещанията в JavaScript се основават на концепцията за thunks и под капака той използва thunks. Разгледайте това видео, което говори за това как thunks могат да се използват за последователност на извиквания на асинхронни функции.
  • Мидълуерът redux-thunk широко използва вътрешно преобразуватели. Това ни позволява да използваме thunks с асинхронна логика вътре, които могат да взаимодействат с методите на Redux store dispatch и getState за приложения на React, използващи Redux за състояние управление.

Всичко за Currying в JavaScript

Нека да разгледаме друга доста полезна концепция във функционалното програмиране в JavaScript.

Кърирането е усъвършенствана техника за работа с функции. Използва се не само в JavaScript, но и в други езици!

Какво е къри?

Къриране е трансформация на функции, която преобразува функция от извикваема като f(a, b, c) в извикваема като f(a)(b)(c).

Ние трансформираме функцията, като я разграждаме по такъв начин, че да може да бъде извикана по стъпка по стъпка начин, вместо да извиква веднага с всички аргументи. Тази техника е много полезна, особено когато нямате достъп до всички необходими параметри наведнъж, за да можете да изпълните логиката във вашата функция.

Нека да разгледаме пример, за да разберем това по-добре. Да предположим, че създаваме проста функция multiplyTwoNos, която умножава две не и връща изчислената стойност.

const multiplyTwoNos = (no1, no2) => {
  return no1 * no2;
};

console.log(multiplyTwoNos(3, 5)); // 15

// A curried function to multiple two nos
const curriedMultiplyTwoNos = function (no1) {
  return function (no2) {
    return no1 * no2;
  };
};

console.log(curriedMultiplyTwoNos(3)(5)); // 15

В горния кодов фрагмент curriedMultiplyTwoNos е функция, която използва къриране, за да трансформира извикването на функция multiplyTwoNos(3, 5) в извикване на функция нещо подобно - curriedMultiplyTwoNos(3)(5). Резултатът е основно същият, но кърирането ни дава повече гъвкавост за изчисляване на умножението на две числа, когато имаме достъп и до двете числа.

Разширено внедряване на Currying

Сега, нека да разгледаме малко по-разширено внедряване на къриране за многоаргументни функции. Първо ще създадем обща функция за къриране и след това ще използваме горния пример за умножаване на някои числа и получаване на изчислената стойност.

// A function that takes a callback function `func` 
// that defines the kind of operation that will be curried.
const curry = function (func) {
  return function curried (...args) {
    // Ensure that we apply the operation defined by `func` 
    // with the max number of arguments supported by `func`.
    if(args.length >= func.length) {
      return func.apply(this, args);
    } else {
      // If number of parameters are insufficient to call `func` to 
      // perform the operation, recursively call `curried` with previously passed
      // parameters as well as new parameters. 
      return function (...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  }
}

В горния код се случват много неща! 😅

Но не се уморявайте, като видите горния код, той всъщност е доста ясен! Нека да видим как работи с пример и тогава ще го разберем правилно.

const multiplyThreeNos = (no1, no2, no3) => {
  return no1 * no2 * no3;
};

let curriedMultiplication = curry(multiplyThreeNos);

console.log(curriedMultiplication(4, 5, 6)); // 120, still callable normally
console.log(curriedMultiplication(4)(5, 6)); // 120, currying of 1st arg
console.log(curriedMultiplication(4)(5)(6)); // 120, full currying

Резултатът от извикването curry(func) е обвивката curried, която изглежда така:

function curried(...args) {
    // Ensure that we apply the operation defined by `func`
    // with the max number of arguments supported by `func`.
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      // If number of parameters are insufficient to call `func` to
      // perform the operation, recursively call `curried` with previously passed
      // parameters as well as new parameters.
      return function (...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };

В горния код използваме някои от понятията като параметри за почивка, Рекурсии и .apply().

По принцип има два клона на код, от които единият може да бъде изпълнен в рамките на функцията curried поради оператора if.

  • Ако броят на аргументите, предадени на curried, е по-голям или равен на броя аргументи, дефинирани за func, тогава просто предайте извикването на func.apply(this, args);, което ще изпълни игнорира допълнителните подадени аргументи, ако има такива.
  • В противен случай, ако броят на аргументите, предадени на curried, е по-малък, тогава ние рекурсивно извикваме curried с по-рано предадени аргументи, както и с новите аргументи, като използваме метода .concat(), докато броят на аргументи за curried не е достатъчно според дефиницията на func.

Забележка: Кърирането изисква функцията да има фиксиран брой аргументи. Функция, която използва параметри за почивка, като например f(...args), не може да бъде карирана по този начин, както е приложено по-горе.

Кога и защо да използваме Currying? 💡

Кърито може да бъде полезно за:

  • Curry може да се използва, когато искате да създадете функция, която ще получава само единични аргументи или ще получава аргументи по-късно във вашия код.
  • Може да се използва за задействане на слушатели на събития.

Нека да разгледаме някои от причините, поради които може да се наложи да използвате Currying:

  • Currying предоставя начин да се уверите, че вашата функция получава всички аргументи, преди да продължите с изпълнението на основната логика на вашата функция.
  • Той разделя вашата функция на множество по-малки функции, които могат да се справят с една отговорност. Това прави вашата функция чиста и по-малко податлива на грешки и странични ефекти.
  • Използва се в парадигмата на функционалното програмиране за създаване на „функции от по-висок ред“.

Заключение

В този блог научихме за thunks и currying, някои от най-полезните, но малко напреднали концепции за функционално програмиране в JavaScript. Говорихме за thunks подробно, като какво представляват thunks, как можете да ги използвате, кога да ги използвате и къде се използват. По същия начин научихме за къри, как да внедрим къри, разширено внедряване на къри и кога да го използваме .

Това е всичко от мен, хора, много ви благодаря, че четете този блог! 🙌
Надявам се, че този блог е бил полезен и ви е дал представа за thunks и curry и как можете да използвате тези много полезни концепции на парадигмата на функционалното програмиране във вашия JavaScript приложения. 😄

преди да приключим,

Чувствайте се свободни да се свържете с мен:
Twitter
Linkedin
GitHub

Повече съдържание в PlainEnglish.io. Регистрирайте се за нашия безплатен седмичен бюлетин. Следвайте ни в Twitter и LinkedIn. Вижте нашия Community Discord и се присъединете към нашия Talent Collective.