Тэкс. Объясняем "на пальцах" суть колбеков в асинхронных методах.
Вот обычная советская функция, которая присваивает объявленной перед её вызовом переменной значение - так, "как все привыкли":
function foo() {
a = 'aaa';
}
var a;
foo();
console.log(a); // 'aaa'
Полный успех.
А давайте сделаем присвоение значения переменной а - асинхронным, вот так:
function foo() {
process.nextTick( () => {
a = 'aaa';
});
}
var a;
foo();
console.log(a); // undefined
Ошибки не случилось - то есть функция переменную видела, к ней благополучно обращалась, - но в текущем "тике" переменной значение, естественно, не присвоено. Причём, оно было ваабщета присвоено - но нам оказалось уже недоступно.
А давайте посмотрим, есть ли оно в следующем тике?
function foo() {
process.nextTick( () => {
a = 'aaa';
});
}
var a;
foo();
console.log(a); // undefined
process.nextTick ( () => {
console.log(a); // 'aaa'
});
Таки есть!
И чтобы нам её (переменную с присвоенным значением) всё таки добыть и употребить, как раз и потребуется колбек-функция. Тырц:
function foo(cb) {
setImmediate( () => { // setImmediate - это правильная директива для асинхронного исполнения
a = 'aaa';
cb(a);
});
}
var a;
foo( data => {
console.log(data); // 'aaa'
});
console.log(a); // а тут, понятно, как и прошлый раз - undefined
Причём, сначала-то будет выведено undefined из последней строчки, а потом - 'aaa' из колбека.
Вот и вся несложная мудрость. Из которой, надеюсь, должно быть понятно, что любой асинхронный метод любую переменную "снаружи" взять-то может - а вот воспользоваться тем, что он с ней сделает, можно только "внутри": либо внутри собственно колбека этого метода, либо внутри промиса (в который обернут, опять же, вызов этого метода с вызовом его колбека) - но "внутри", и не иначе.
В "борьбе с асинхронностью" приходится всё время придумывать конструкции для добывания нужных значений в нужном месте. И это хорошо.
Извените за внимание.