Обработка одновременных обновлений asyn c в локальном состоянии - PullRequest
4 голосов
/ 15 февраля 2020

У меня есть серия асинхронных вызовов, которые читают из локального состояния S , выполняют некоторые вычисления на основе его текущего значения и возвращают новое обновленное значение локального состояния S '

Все это происходит во время выполнения, поэтому я очень мало контролирую порядок этих операций. Это упрощенная версия того, что у меня есть.

type State = {
  state: number
}

let localState: State = {
  state: 1000
}

const promiseTimeout = (time: number, value: number) => () => new Promise(
    (resolve: (n: number) => void) => setTimeout(resolve, time, value + time)
  );


const post: (n: number, currentState: State) => Promise<void> = (n, c) => promiseTimeout(n, c.state)()
  .then(res => {
    localState.state = res
    console.log(localState)
  })

post(1000, localState); // localState at call time is 1000
post(3000, localState); // localState at call time is still 1000
// when both promises resolve, the final value of localState will be 4000 instead of 5000

ссылка на игровую площадку

Эта модель явно не работает, так как оба вызова post будут читать одинаково значение localState, тогда как вместо этого они должны выполняться последовательно.

Если бы все вызовы уже были определены во время компиляции, я мог бы просто иметь что-то вроде

post(1000, localState)
  .then(() => post(3000, localState)) // localState at call time is now 2000

Как бы я go о решении этого?

Ответы [ 3 ]

3 голосов
/ 15 февраля 2020

Один из подходов состоит в том, чтобы post подключиться к обещанию, а не работать непосредственно с объектом состояния. Это обещание может быть сохранено в самом объекте состояния. Он начинается с государственного объекта. post обновляет его следующим образом:

const post = (n, state) => {
    return state.promise = state.promise
        .then(state => {
            // ...do stuff here that updates (or replaces) `state`...
            return state;
        }));
};

Вот пример (в JavaScript, но вы можете добавить аннотации типов), используя asyncAction (это как ваш promiseTimeout, но без он возвращает функцию, которую мы вызываем немедленно, не

"use strict";

let localState = {
    state: 1000
};
localState.promise = Promise.resolve(localState);

// I'm not sure why this *returns* a function that we
// have to call, but...
const promiseTimeout = (time, value) => () => new Promise((resolve) => setTimeout(resolve, time, value + time));
  
const post = (n, state) => {
    return state.promise = state.promise
        .then(state => promiseTimeout(n, state.state)().then(newValue => {
            state.state = newValue;
            console.log(state.state);
            return state;
        }));
};

console.log("Running...");
post(1000, localState); // localState at call time is 1000
post(3000, localState); // localState at call time is still 1000

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

Вот что в TypeScript (с небольшим количеством взлома в одном месте, вы, вероятно, можете улучшить это); ссылка на игровую площадку .

type State = {
  state: number,
  promise: Promise<State>
};

let localState: State = (() => {
    const s: Partial<State> = {
        state: 1000
    };
    // There's probably a better way to handle this than type assertions, but...
    s.promise = Promise.resolve(s as State);
    return s as State;
})();

// I'm not sure why this *returns* a function that we
// have to call, but...
const promiseTimeout = (time: number, value: number) => () => new Promise(
    (resolve: (n: number) => void) => setTimeout(resolve, time, value + time)
);

const post = (n: number, state: State): Promise<State> => {
    return state.promise = state.promise
        .then(state => promiseTimeout(n, state.state)().then(newValue => {
            state.state = newValue;
            console.log(state.state);
            return state;
        }));
};

console.log("Running...");
post(1000, localState); // localState at call time is 1000
post(3000, localState); // localState at call time is still 1000

Стоит отметить, что в подобных ситуациях, когда состояние может изменяться асинхронно, как это, часто стоит создать new объект состояния при его изменении, а не изменении существующего - например, обрабатывать аспекты состояния как неизменные.

1 голос
/ 15 февраля 2020

Это проблема, с которой я лично сталкивался во многих случаях. Мое решение состоит в том, чтобы создать класс очереди, отвечающий за выполнение всех Promise во взаимном исключении . Я называю это PromiseQueue:

class PromiseQueue {
    constructor() {
        this._queue = new Array(); // Or an LinkedList for better performance
        this._usingQueue = false;
    }

    /**
     * Adds an element to the queue and runs the queue. It resolves when the promise has been executed and resolved.
     *
     * @param {Promise<any>} promise
     */
    add(promise) {
        const self = this;
        return new Promise((resolve, reject) => {
            const promiseData = {
                promise,
                resolve,
                reject,
            };
            self._queue.push(promiseData);
            self._runQueue();
        });
    }

    async _runQueue() {
        if (!this._usingQueue && this._queue.length > 0) {
            this._usingQueue = true;
            const nextPromiseData = this._queue.shift();
            const { promise, resolve, reject } = nextPromiseData;
            try {
                const result = await promise();
                resolve(result);
            } catch (e) {
                reject(e);
            }
            this._usingQueue = false;
            this._runQueue();
        }
    }
}

Тогда вы будете использовать это так (не проверено):

const myPromiseQueue = new PromiseQueue();

// This way you are making sure that the second post
// will be executed when the first one has finished
myPromiseQueue.add(async() => await post(1000, localState));
myPromiseQueue.add(async() => await post(3000, localState));
0 голосов
/ 15 февраля 2020

У меня нет опыта работы с TypeScript, поэтому вам придется выполнить преобразование самостоятельно.

Вы можете добавить метод queue к вашему состоянию, который принимает обратный вызов. Если обратный вызов возвращает обещание, он будет ждать его завершения sh. Если нет, следующий элемент в очереди выполняется немедленно.

function createQueue() {
  var promise = Promise.resolve();
  return function (fn) {
    promise = promise.then(() => fn(this));
    return promise;
  };
}

const localState = { state: 1000, queue: createQueue() };

const timeout = (...args) => new Promise(resolve => setTimeout(resolve, ...args));
const promiseTimeout = (time, value) => timeout(time, value + time);

const post = (time, state) => state.queue(() => {
  return promiseTimeout(time, state.state).then(result => {
    state.state = result;
    console.log(state.state);
  });
});

post(1000, localState).then(() => console.log("post 1000 complete"));
post(3000, localState).then(() => console.log("post 3000 complete"));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...