От вызова до получение данных. Как работают функции?

Здравствуйте! Прошу помочь понять, как работают функции, получающие данные с базы данных. Ниже я опишу моменты, которые не ясны в этих функциях. Прошу помочь рассказать, как это всё работает.
Имеется 3 файла: main.js, Articles, db

main.js

import * as ArticlesModel from './articles';

ArticlesModel.all((articles) => {
    console.log('articles count = ' + articles.length);
    
    // берём случайный индекс
    let ind = Math.floor(Math.random() * articles.length);
    console.log('select index ' + ind + ', id = ' + articles[ind].id)

    // получаем статью по id
    ArticlesModel.one(articles[ind].id, (article) => {
        console.log(article);

        // пробуем удалить её
        ArticlesModel.remove(article.id, (res) => {
            console.log('что с удалением? - ' + res);

            // а сколько статей в базе сейчас
            ArticlesModel.all((articles) => {
                console.log('articles count = ' + articles.length);
            }, (error) => {
                console.log(error + ' in articles list after delete');
            });
        }, (error) => {
            console.log(error + ' in articles delete');
        })

    }, (error) => {
        console.log(error + ' in articles one');
    });

}, (error) => {
    console.log(error + ' in articles list');
});

articles.js

import * as serverApi from './db';

function all(onSuccess, onError){
    serverApi.all((response) => {
        let info = JSON.parse(response);

        if(info.code === 200){
            onSuccess(info.data);
        }
        else{
            onError(info.status);
        }
    });
}

function one(id, onSuccess, onError){
    serverApi.get(id, (response) => {
        let info = JSON.parse(response);

        if(info.code === 200){
            onSuccess(info.data);
        }
        else{
            onError(info.status);
        }
    });
}

function remove(id, onSuccess, onError){
    serverApi.remove(id, (response) => {
        let info = JSON.parse(response);

        if(info.code === 200){
            onSuccess(info.data);
        }
        else{
            onError(info.status);
        }
    });
}

export {all, one, remove};

db.js

/**
 * Глобальная вероятность успеха для удобства тестирования
 */
const GLOBAL_PROPABILITY = 1;
const BAD_JSON_PROPABILITY = 0;

/**
 * Получить все записи из хранилища
 * @param {callable} onAnswer Функция, обрабатывающая ответ от сервера в формате JSON 
 */
export function all(onAnswer){
    TimeoutPropabiliry(300, GLOBAL_PROPABILITY, () => {
        onAnswer(serverAnswer(articlesStorage));
    }, () => {
        onAnswer(serverAnswer('', 100500, "Propability Error"));
    });
}

/**
 * Получить статью по id
 * @param {int} id Id статьи
 * @param {callable} onAnswer Функция, обрабатывающая ответ от сервера в формате JSON 
 */
export function get(id, onAnswer){
    TimeoutPropabiliry(300, GLOBAL_PROPABILITY, () => {
        onAnswer(serverAnswer(articlesStorage[mapArticles[id]]));
    }, () => {
        onAnswer(serverAnswer('', 100500, "Propability Error"));
    });
}

/**
 * Удалить статью из базы
 * @param {int} id Id статьи
 * @param {callable} onAnswer Функция, обрабатывающая ответ от сервера в формате JSON  
 */
export function remove(id, onAnswer){
    TimeoutPropabiliry(300, GLOBAL_PROPABILITY, () => {
        if(id in mapArticles){
            let num = mapArticles[id];
            delete mapArticles[id];
            articlesStorage.splice(num, 1);
            onAnswer(serverAnswer(true));
        }
        else{
            onAnswer(false);
        }
    }, () => {
        onAnswer(serverAnswer('', 100500, "Propability Error"));
    });
}

/* полуприватная часть, вдруг захотите сделать промис :) */
function TimeoutPropabiliry(time, probability, onSuccess, onError){
    window.setTimeout(() => {
        Math.random() < probability ? onSuccess() : onError();
    }, time);
}

function serverAnswer(data, code = 200, status = "OK"){
    if(Math.random() < BAD_JSON_PROPABILITY){
        return 'incoorect json';
    }

    return JSON.stringify({
        code, 
        status,
        data
    });
}

/*  приватная часть */ 
let articlesStorage = [
    {
        id: 1,
        title: 'Профисификация кода',
        dt: '2018-12-06',
        text: 'Код без промисов бывает жестью, но и с ними можно изобразить много странного.'
    },
    {
        id: 2,
        title: 'Итераторы и генераторы',
        dt: '2018-12-01',
        text: 'Сначала пугают всех, кто к ним прикасается, а Symbol кажется бредом.'
    },
    {
        id: 5,
        title: 'Javascript',
        dt: '2018-12-02',
        text: 'Всё равно хороший язык программирования.'
    }
];

let mapArticles = {};

articlesStorage.forEach((item, i) => {
    mapArticles[item.id] = i;
});

Вопросы

  1. Правильно ли я понимаю, что вызов функции происходит в файле main(к примеру ArticlesModel.one), далее работа интрепретатора переходит в файл articles(function one), которая запрашивает данные в файле db(function get)? И что конкретно принимает и будет возвращать каждая из этих функции. Это очень важно!
  2. Не понятны параметры в этой функции:

ArticlesModel.one(articles[ind].id, (article) => {

  • articles[ind].id - будет приведён, к примеру articles1.id?
  • что это за коллбэк вторым параметром и что за article он принимает?
  • если смотреть на функцию one, то там 3 параметра (id, onSuccess, onError), а мы отправляем только два.
  1. В файле article, внутри каждой написано serverApi.функция… я понимаю, что та же функция all берётся из файла db. Но что стоит перед этими функциями? Что такое serverApi? И что за response принимается в функции all
  2. Не понятно, как именно работает функция all в файле db

Прошу помочь разобраться!

Поток вызовов начинается в main.js с этого вызова: ArticlesModel.all. В целом то как ты описываешь - корректная (но не точная, что не критично в данном случае) модель как работает код.

То что принимает функция опрделяется в моменте где она вызывается. То что она возвращает описывается в месте где она определяется.

Нет. articles содержит массив объектов вида

{
id: ... ,
...
}

и код articles[ind].id просто вычисляется в значение свойства id ind-ого объекта из массива articles.

Посмотри в определение one на второй аргумент:

function one(id, onSuccess, onError){
    serverApi.get(id, (response) => {
        let info = JSON.parse(response);

        if(info.code === 200){
            onSuccess(info.data);
        }
        else{
            onError(info.status);
        }
    });
}

onSuccess - это функция которая будет вызвана функцией one в случае успеха операции. В нее параметром передадут результат успешной операции.

Рассмотрим же код схематически:

one(x, (res) => {
    console.log(res)
})

в нем вызывается one с двумя параметрами. Первый параметр это примитив, там не интересен ибо мы понимаем и знаем как работает передача примитивов в параметрах.

Второй параметр интереснее: это функция. Вызывая one мы знаем ее API, и знаем что она выполняет асинхронную операцию, и не может дать результат выполнения как возвращаемое значение. Зато мы можем передать в one функцию, которую one вызовет когда будет готов результат операции. one при вызове функции-аргумента передаст в нее параметром результат операции. Что с ним делать определяем мы так как мы описываем тело функции-аргумента.

Итого: суть второго параметра - быть функцией с аргументом, с описанием что мы хотим сделать с этим аргументом. Эту функцию вызовет one когда значение для аргумента будет готово. Мы только что описали суть коллбеков.

js не форсит чтобы количество параметров передаваемых с вызовом функции соответствовало количеству аргументов с которыми она объявлена. Когда параметр не передан при вызове, js записывает значение undefined в переменную аргумента.

В данном случае onError внутри one будет undefined что приведет к runtime error если if(info.code === 200){ условие не выполнится. В целом обработку ошибок нужно делать, но ее часто “опускают” в примерах, mvp, демках и других курсах.

Что ты имеешь в виду тут?

Но что стоит перед этими функциями?

Тут удобно думать о модулях как об объектах. Смотрим в файл db.js, и представляем что каждый export это по сути объявления свойства в объекте который описывается файлом db.js. Теперь смотрим на использование db.js в articles.js: import * as serverApi from './db'; - говорим что все экспорты из модуля записываем в объект с названием serverApi. serverApi.all, получается, вызов функции all из файла db.js.

Она использует TimeoutPropabiliry (описка в названии + название должно быть camelCase, потому что это не класс) чтобы вызвать успешный коллбек через 300 миллисекунд с вероятностью GLOBAL_PROPABILITY. TimeoutPropabiliry через 300 миллисекунд с вероятностью GLOBAL_PROPABILITY вызовет первый коллбек, и с обратной вероятностью - второй.

Таким образом автор моделирует ненадежное окружение (типа база упала или порвалась связь между сервером и базой итд), в котором ошибки происходят с некоторой вероятностью.

Ответ помог? Остались непонятки?