Это вторая часть серии Асинхронные шаблоны JS. В предыдущем посте я писал об обратных вызовах и о том, как они помогают нам решать некоторые распространенные программные головоломки, такие как параллелизм. В этом посте мы поговорим о несколько другом подходе к обратным вызовам, thunks.

Давайте сначала пересмотрим важную концепцию, лежащую в основе преобразователей.

Замыкание — это комбинация функции, заключенной со ссылками на окружающее ее состояние (лексическое окружение). МДН

Давайте посмотрим на пример, предоставленный MDN (я намеренно изменил некоторые комментарии и строки для лучшего понимания).

function makeFunc() {
    var name = 'Mozilla';
    function displayName() {
        console.info(name);
    }
    return displayName;
}
var myFunc = makeFunc();
myFunc();   
// it executes the function displayName() 
// using all the private state (in this case the string) 
// from its parent function makeFunc()

Как видно из предыдущего фрагмента, замыкание позволяет функции запоминать набор состояний (аналогично обычной системе памяти) и играть с ними. Это очень просто, но применение в асинхронных задачах делает его очень интересным.

На практике преобразователь — это возвращаемая функция в функции, как в следующем коде:

function helloWorld() {
// Some private state
    return function thunk(cb){
                cb()
    }
}
let th = helloWorld()
th(cb) 
// Whenever the function helloWorld() has finished its instructions,
// we pass to the return function a callback with more instructions,
// resulting in a sequential flow!

Предыдущий пример — это пример асинхронных преобразователей, но давайте не будем усложнять, мы немного углубимся в асинхронные преобразователи.

Технически говоря, преобразователь — это контейнер вокруг набора состояний, это функция, которая возвращает другую функцию, поэтому она имеет доступ к родительскому частному состоянию (закрытие).

Давайте рассмотрим следующий синхронный преобразователь:

function sumThunk(a, b) {
    return a+b
}
function commonFunction(){
    const a = 10 
    const b = 10  
    return sumThunk(a,b) 
}
const total = commonFunction()
console.info(total) // 20

Асинхронные преобразователи ведут себя очень похоже, однако мы передаем в качестве параметра функцию (обратный вызов), поэтому всякий раз, когда значение готово (например, выборка некоторых данных), оно выполняет функцию (в результате получается последовательно и блокирующий способ сортировки асинхронных задач).

Давайте напишем программу, которая извлекает некоторые данные, но на этот раз мы напишем ее настолько интуитивно понятно, насколько наш мозг, скорее всего, сортировал бы ее.

function fetchAPI(rq, time) {
    const apiResponses = {
        'file1': 'First File',
        'file2': 'Second file'
    }
    setTimeout(function () {
        console.info(apiResponses[rq])
    }, time)
}
function executer(rq, time) {
        fetchAPI(rq, time) 
}
let th1 = executer('file1' , 3000)
let th2 = executer('file2' , 100)
// Second file
// First file

Вывод предыдущего примера отличается от того, что мы изначально ожидали, мы хотим, чтобы file1 извлекался первым, а затем file2. Настоящая проблема здесь связана с движком JS, потому что он отвечает за максимально быстрое выполнение инструкций, не беспокоясь о ожидании медленных ответов, поэтому здесь происходит то, что, поскольку file1 требуется 3 секунды для выполнения, а file2 просто 100 секунд, JS-движок сначала выберет выполнение file2, чем file1, вопреки тому, что мы действительно хотим.

Теперь давайте рассмотрим следующий пример с использованием thunks для правильного управления асинхронными задачами (последовательно и с блокировкой).

function fetchAPI(rq, time, cb) {
    const apiResponses = {
        'file1': 'First File',
        'file2': 'Second file'
    }
    setTimeout(function () {
        cb(apiResponses[rq]) // - function ready() - lazy executed!
    }, time)
}
function executer(rq, time) {
// we return a thunk with the - function ready() - as a 'future' callback
    return function thunk(cb) {
        fetchAPI(rq, time, cb) 
    }
}
// We return the function of  - function executer() - in the variable th, 
// as it is a function we can pass a param(in this case a callback - function ready() ), 
let th1 = executer('file1' , 3000)
let th2 = executer('file2' , 100)
th1(function ready(salida){
    console.info(salida) 
  // When JS engine comes in this line the first task has completed, 
  // so now we launch the second one
    th2(function ready(salida) {
        console.info(salida)
    })
})

Ссылки