Обработка асинхронных вызовов (Firebase) в функциях

Несколько раз я столкнулся с проблемой синхронных и асинхронных функций с использованием Firebase. Моя проблема часто заключается в том, что мне нужно сделать асинхронный вызов Firebase внутри функции, которую я написал. В качестве простого примера предположим, что мне нужно рассчитать и отобразить скорость объекта, а моя Firebase хранит расстояние и время:

function calcVelocity() {
    var distance, time, velocity;

    firebaseRef.once('value', function(snapshot) {
        distance = snapshot.val().distance;
        time = snapshot.val().time;

        velocity = distance / time;
    });
    return velocity;
}

$("#velocity").html(calcVelocity());

Конечно, приведенный выше код не будет работать, потому что firebaseRef.once() является асинхронным вызовом, поэтому velocity еще не установлен, когда мы достигнем return velocity;. Если мы поместим return внутри функции обратного вызова .on(), то ничего не будет возвращено вообще.

Одним из решений было бы сделать мою функцию calcVelocity() асинхронной.

Другим решением было бы хранить кэшированную версию Firebase, которая читается синхронно, но обновляется асинхронно из Firebase.

Является ли одно из этих решений лучше, чем другое? И есть ли лучшее решение?

Ответ 1

Вы прикрепили две возможности: либо сделать вашу функцию асинхронной, либо кэшировать последние данные Firebase, чтобы вы могли получить доступ к ней синхронно. Какой из них вы используете, это просто вопрос предпочтения и удобства, учитывая контекст приложения, которое вы пишете.

Например, мы заметили, что "action games" обычно управляются жестким циклом рендеринга, а не событиями изменения данных firebase. Поэтому имеет смысл кэшировать последние данные Firebase для использования в вашем цикле рендеринга. Например:

var latestSnapshot = null;
firebaseRef.on('value', function(snap) { latestSnapshot = snap; });

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

Ответ 2

Другой подход заключается в использовании стратегии Promise. jQuery имеет отличный вариант.

function calcVelocity() {
    var distance, time, velocity, def = $.Deferred();

    firebaseRef.once('value', function(snapshot) {
        distance = snapshot.val().distance;
        time = snapshot.val().time;

        def.resolve( distance / time );
    });
    return def.promise();
}

calcVelocity().then(function(vel) { $("#velocity").html(vel); });

Имейте в виду, что snapshot.val().distance; может возвращать ошибку, если snapshot.val() возвращает null!

Ответ 3

То же самое, что и в ответе @Kato, но с встроенным promises в Firebase будет выглядеть примерно так

function calcVelocity(snapshot) {
    var distance, time, velocity;

    distance = snapshot.val().distance;
    time = snapshot.val().time;

    return distance / time;
}

function getVelocity() {
return firebaseRef.once('value').then(calcVelocity);
}

getVelocity().then(function(vel) { $("#velocity").html(vel); });