Firebase: достаточно ли это, чтобы проверить, существует ли ссылка перед началом транзакции - PullRequest
0 голосов
/ 29 августа 2018

Интересно, достаточно ли это, чтобы проверить, существует ли ссылка? ДО того, как я начну транзакцию по этой ссылке? например: с помощью .once ('value') и snapshot.exists ()

Я имею в виду, если проверка находится за пределами транзакции, нет ли риска, что другой пользователь удалит ссылку сразу после проверки и перед функцией исполнителя транзакций?

==== отредактировано, чтобы включить минимальный полный код =====

вот мои данные в базе данных в реальном времени:

activeOffers
    -LKohyZ58cnzn0vCnt9p
        details
            direction: "city"
            seatsCount: 2
            timeToGo: 5
        uid: "-ABSIFJ0vCnt9p8387a"    ---- offering user

А вот мой поток кода:

===== index.js =====

entries = require('./entries');

/// cloud function
exports.TEST_askOfferSeats = functions.https.onCall((data, context) => {
    console.log('data: ' + JSON.stringify(data));
    return entries.askSeats(data);
});

вот мои тестовые данные, отправленные почтальоном:

{
 "data": 
  {
     "uid": "-FGKKSDFGK12387sddd",    ---- the requesting/asking user
     "id": "-LKpCACQlL25XTWJ0OV_",
     "details":
     {
          "direction": "city",
          "seatsCount": 1,
          "timeToGo": 5
     }
  }
}

===== records.js =======

exports.askSeats = function(data) {
const TAG = '[askSeats]: ';

var entryRef = db.ref('activeOffers/' + data.id);
return globals.exists(entryRef)
    .then((found)=>{
        if (found) {
            return dealSeats(entryRef, data);
        } else {
            return 'Offer not found [' + data.id + ']';
        }
    });
}

===== globals.js ======

exports.exists = (ref)=>{
    return ref.once('value')
        .then((snapshot)=>{
            return (snapshot.exists());
        });
}

===== records.js =====

dealSeats = function(entryRef, data) {
    const TAG = '[dealSeats]: ';
    return entryRef.transaction((entry)=>{
        if (entry) {
            if ((entry.deals) && (entry.deals[data.uid])) {
                throw new Error('You've already made a deal.');
            } else if (entry.details.seatsCount >= data.details.seatsCount) {
                entry.details.seatsCount -= data.details.seatsCount;
                var deal = [];
                deal.status = 'asked';
                deal.details = data.details;
                if (!entry.deals) {
                    entry.deals = {};
                }
                entry.deals[data.uid] = deal;
            } else {
                throw new Error('Not enought seats.');
            }
        }
        return entry;
    })
    .then((success)=>{
        return success.snapshot.val();
    })
    .catch((error)=>{
        return Promise.reject(error);
    });
}

Кстати: это «выбросить новую ошибку (......)», это правильный способ разорвать транзакцию?

========= обновлено с окончательным источником ===

Спасибо Дагу Стивенсону.

Итак, вот мой последний источник, который работает нормально. Если кто-то видит потенциальную проблему, пожалуйста, дайте мне знать. Спасибо.

dealSeats = function(entryRef, data) {
    const TAG = '[dealSeats]: ';
    var abortReason;

    return entryRef.transaction((entry)=>{
        if (entry) {
            if ((entry.deals) && (entry.deals[data.uid])) {
                abortReason = 'You already made a reservation';
                return; // abort transaction
            } else if (entry.details.seatsCount >= data.details.seatsCount) {
                entry.details.seatsCount -= data.details.seatsCount;
                var deal = [];
                deal.status = 'asked';
                deal.details = data.details;
                if (!entry.deals) {
                    entry.deals = {};
                }
                entry.deals[data.uid] = deal;
                // Reservation is made
            } else {
                abortReason = 'Not enought seats';
                return; // abort transaction
            }
        }
        return entry;
     })
    .then((result)=>{ // resolved
        if (!result.committed) { // aborted
            return abortReason;
        } else {
            let value = result.snapshot.val();
            if (value) {
                return value;
            } else {
                return 'Offer does not exists';
            }
        }
    })
     .catch((reason)=>{ // rejected
        return Promise.reject(reason);
    });
}

1 Ответ

0 голосов
/ 30 августа 2018

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

Если вы хотите действительно атомарное обновление, проверяйте только значение, участвующее в транзакции, в самой транзакции и принимайте решение о том, что делать в обработчике транзакции.

...