diff --git a/explorer/src/lib/containers/statistics-panel/StatisticsPanel.svelte b/explorer/src/lib/containers/statistics-panel/StatisticsPanel.svelte
index fa212eac88..9cbe0e5c2f 100644
--- a/explorer/src/lib/containers/statistics-panel/StatisticsPanel.svelte
+++ b/explorer/src/lib/containers/statistics-panel/StatisticsPanel.svelte
@@ -7,7 +7,7 @@
mdiCurrencyUsd,
mdiSwapVertical,
} from "@mdi/js";
- import { onDestroy, onMount } from "svelte";
+ import { onDestroy } from "svelte";
import { createCurrencyFormatter, luxToDusk } from "$lib/dusk/currency";
import { createCompactFormatter } from "$lib/dusk/value";
@@ -15,12 +15,12 @@
import { Icon } from "$lib/dusk/components";
import { DataGuard, WorldMap } from "$lib/components";
import { duskAPI } from "$lib/services";
- import { appStore } from "$lib/stores";
import {
createDataStore,
createPollingDataStore,
} from "$lib/dusk/svelte-stores";
import { onNetworkChange } from "$lib/lifecyles";
+
import "./StatisticsPanel.css";
const valueFormatter = createCurrencyFormatter("en", "DUSK", 0);
@@ -37,36 +37,31 @@
const nodeLocationsStore = createDataStore(duskAPI.getNodeLocations);
const marketDataStore = createDataStore(duskAPI.getMarketData);
-
- const getNodeLocations = () => {
- nodeLocationsStore.getData($appStore.network);
- };
-
- const getMarketData = () => {
- marketDataStore.getData($appStore.network);
- };
-
const pollingStatsDataStore = createPollingDataStore(
duskAPI.getStats,
STATS_FETCH_INTERVAL
);
- onNetworkChange(pollingStatsDataStore.start);
- onNetworkChange(getNodeLocations);
- onNetworkChange(getMarketData);
- onDestroy(pollingStatsDataStore.stop);
+ onNetworkChange((network) => {
+ marketDataStore.getData(network);
+ nodeLocationsStore.getData(network);
+ pollingStatsDataStore.start(network);
+ });
- $: ({ data } = $nodeLocationsStore);
+ onDestroy(pollingStatsDataStore.stop);
+ $: ({ data: marketData } = $marketDataStore);
+ $: ({ data: nodesData } = $nodeLocationsStore);
+ $: ({ data: statsData } = $pollingStatsDataStore);
$: statistics = [
[
{
- data: $marketDataStore.data?.currentPrice.usd,
+ data: marketData?.currentPrice.usd,
icon: mdiCurrencyUsd,
title: "Dusk Price",
},
{
- data: $marketDataStore.data?.marketCap.usd,
+ data: marketData?.marketCap.usd,
icon: mdiCurrencyUsd,
title: "Total Market Cap",
},
@@ -74,15 +69,15 @@
[
{
- data: $pollingStatsDataStore.data?.activeStake
- ? luxToDusk($pollingStatsDataStore.data.activeStake)
+ data: statsData?.activeStake
+ ? luxToDusk(statsData.activeStake)
: undefined,
icon: duskIcon,
title: "Current Staked Amount",
},
{
- data: $pollingStatsDataStore.data?.waitingStake
- ? luxToDusk($pollingStatsDataStore.data.waitingStake)
+ data: statsData?.waitingStake
+ ? luxToDusk(statsData.waitingStake)
: undefined,
icon: duskIcon,
title: "Next Epoch Staked Amount",
@@ -91,12 +86,12 @@
[
{
- data: $pollingStatsDataStore.data?.lastBlock,
+ data: statsData?.lastBlock,
icon: mdiCubeOutline,
title: "Last Block",
},
{
- data: $pollingStatsDataStore.data?.txs100blocks.transfers,
+ data: statsData?.txs100blocks.transfers,
icon: mdiSwapVertical,
title: "TX Last 100 Blocks",
},
@@ -104,22 +99,17 @@
[
{
- data: $pollingStatsDataStore.data?.activeProvisioners,
+ data: statsData?.activeProvisioners,
icon: mdiAccountGroupOutline,
title: "Provisioners",
},
{
- data: $pollingStatsDataStore.data?.waitingProvisioners,
+ data: statsData?.waitingProvisioners,
icon: mdiAccountGroupOutline,
title: "Next Epoch Provisioners",
},
],
];
-
- onMount(() => {
- getNodeLocations();
- getMarketData();
- });
@@ -143,6 +133,6 @@
{/each}
-
+
diff --git a/explorer/src/lib/dusk/svelte-stores/__tests__/createDataStore.spec.js b/explorer/src/lib/dusk/svelte-stores/__tests__/createDataStore.spec.js
index 6e06288170..8fefbc27a0 100644
--- a/explorer/src/lib/dusk/svelte-stores/__tests__/createDataStore.spec.js
+++ b/explorer/src/lib/dusk/svelte-stores/__tests__/createDataStore.spec.js
@@ -9,13 +9,16 @@ import {
} from "vitest";
import { get } from "svelte/store";
+import { rejectAfter, resolveAfter } from "$lib/dusk/promise";
+
import { createDataStore } from "..";
describe("createDataStore", () => {
- const data = {};
+ const data1 = { a: 1 };
+ const data2 = { a: 2 };
const error = new Error("some error message");
const args = [1, "a", new Date()];
- const dataRetriever = vi.fn().mockResolvedValue(data);
+ const dataRetriever = vi.fn().mockResolvedValue(data1);
/** @type {DataStore} */
let dataStore;
@@ -34,9 +37,10 @@ describe("createDataStore", () => {
vi.useRealTimers();
});
- it("should create a readable data store and expose a `getData` service method", () => {
+ it("should create a readable data store and expose `getData` and `reset` as service method", () => {
expect(dataRetriever).not.toHaveBeenCalled();
expect(dataStore).toHaveProperty("getData", expect.any(Function));
+ expect(dataStore).toHaveProperty("reset", expect.any(Function));
expect(dataStore).toHaveProperty("subscribe", expect.any(Function));
expect(dataStore).not.toHaveProperty("set");
expect(get(dataStore)).toStrictEqual({
@@ -47,6 +51,11 @@ describe("createDataStore", () => {
});
it("should set the loading property to `true` when `getData` is called and then fill the data and set loading to `false` if the promise resolves", async () => {
+ const expectedState = {
+ data: data1,
+ error: null,
+ isLoading: false,
+ };
const dataPromise = dataStore.getData(...args);
expect(dataRetriever).toHaveBeenCalledTimes(1);
@@ -59,17 +68,18 @@ describe("createDataStore", () => {
await vi.advanceTimersToNextTimerAsync();
- expect(dataPromise).resolves.toStrictEqual({
- data,
- error: null,
- isLoading: false,
- });
- expect(dataPromise).resolves.toStrictEqual(get(dataStore));
+ expect(await dataPromise).toStrictEqual(expectedState);
+ expect(get(dataStore)).toStrictEqual(expectedState);
});
it("should set the loading property to `true` when `getData` is called and then set the error and the loading to `false` if the promise rejects", async () => {
dataRetriever.mockRejectedValueOnce(error);
+ const expectedState = {
+ data: null,
+ error,
+ isLoading: false,
+ };
const dataPromise = dataStore.getData(...args);
expect(dataRetriever).toHaveBeenCalledTimes(1);
@@ -82,16 +92,102 @@ describe("createDataStore", () => {
await vi.advanceTimersToNextTimerAsync();
- expect(dataPromise).resolves.toStrictEqual({
+ expect(await dataPromise).toStrictEqual(expectedState);
+ expect(get(dataStore)).toStrictEqual(expectedState);
+ });
+
+ it("should ignore the previous call if `getData` is called while the promise is still pending", async () => {
+ dataRetriever
+ .mockImplementationOnce(() => resolveAfter(1000, data1))
+ .mockResolvedValueOnce(data2);
+
+ /** @type {DataStoreContent} */
+ let expectedState = {
+ data: data2,
+ error: null,
+ isLoading: false,
+ };
+
+ dataStore.getData(...args);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(1);
+ expect(dataRetriever).toHaveBeenCalledWith(...args);
+
+ let dataPromise = dataStore.getData(...args);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(2);
+ expect(dataRetriever).toHaveBeenNthCalledWith(2, ...args);
+ expect(await dataPromise).toStrictEqual(expectedState);
+ expect(get(dataStore)).toStrictEqual(expectedState);
+
+ // waiting for the first promise to resolve
+ await vi.advanceTimersToNextTimerAsync();
+
+ expect(get(dataStore)).toStrictEqual(expectedState);
+
+ dataRetriever
+ .mockImplementationOnce(() => rejectAfter(1000, error))
+ .mockResolvedValueOnce(data2);
+
+ dataStore.getData(...args);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(3);
+ expect(dataRetriever).toHaveBeenNthCalledWith(3, ...args);
+
+ expectedState = {
+ data: data2,
+ error: null,
+ isLoading: false,
+ };
+ dataPromise = dataStore.getData(...args);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(4);
+ expect(dataRetriever).toHaveBeenNthCalledWith(4, ...args);
+ expect(await dataPromise).toStrictEqual(expectedState);
+ expect(get(dataStore)).toStrictEqual(expectedState);
+
+ // waiting for the first promise to resolve
+ await vi.advanceTimersToNextTimerAsync();
+
+ expect(get(dataStore)).toStrictEqual(expectedState);
+
+ dataRetriever
+ .mockImplementationOnce(() => resolveAfter(1000, data1))
+ .mockRejectedValueOnce(error);
+
+ dataStore.getData(...args);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(5);
+ expect(dataRetriever).toHaveBeenNthCalledWith(5, ...args);
+
+ expectedState = {
data: null,
error,
isLoading: false,
- });
- expect(dataPromise).resolves.toStrictEqual(get(dataStore));
+ };
+ dataPromise = dataStore.getData(...args);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(6);
+ expect(dataRetriever).toHaveBeenNthCalledWith(6, ...args);
+ expect(await dataPromise).toStrictEqual(expectedState);
+ expect(get(dataStore)).toStrictEqual(expectedState);
+
+ // waiting for the first promise to resolve
+ await vi.advanceTimersToNextTimerAsync();
+
+ expect(get(dataStore)).toStrictEqual(expectedState);
});
- it("should not call the data retriever when `getData` is called and the promise is still pending", async () => {
- const dataPromise = dataStore.getData(1);
+ it("should clear the error and leave the existing data while the promise is pending and clear the data when it ends with a failure", async () => {
+ dataRetriever.mockRejectedValueOnce(error);
+
+ /** @type {DataStoreContent} */
+ let expectedState = {
+ data: null,
+ error,
+ isLoading: false,
+ };
+ let dataPromise = dataStore.getData();
expect(get(dataStore)).toStrictEqual({
data: null,
@@ -99,72 +195,153 @@ describe("createDataStore", () => {
isLoading: true,
});
- const dataPromise2 = dataStore.getData(2);
- const dataPromise3 = dataStore.getData(3);
-
- expect(dataPromise2).toBe(dataPromise);
- expect(dataPromise3).toBe(dataPromise);
-
await vi.advanceTimersToNextTimerAsync();
- expect(dataRetriever).toHaveBeenCalledTimes(1);
- expect(dataRetriever).toHaveBeenCalledWith(1);
- expect(dataPromise).resolves.toStrictEqual({
- data,
+ expect(await dataPromise).toStrictEqual(expectedState);
+ expect(get(dataStore)).toStrictEqual(expectedState);
+
+ expectedState = {
+ data: data1,
error: null,
isLoading: false,
+ };
+ dataPromise = dataStore.getData();
+
+ expect(get(dataStore)).toStrictEqual({
+ data: null,
+ error: null,
+ isLoading: true,
});
- expect(dataPromise).resolves.toStrictEqual(get(dataStore));
- });
- it("should clear the error and leave the existing data while the promise is pending and clear the data when it ends with a failure", async () => {
- dataRetriever.mockRejectedValueOnce(error);
+ await vi.advanceTimersToNextTimerAsync();
- let dataPromise = dataStore.getData();
+ expect(await dataPromise).toStrictEqual(expectedState);
+ expect(get(dataStore)).toStrictEqual(expectedState);
- await vi.advanceTimersToNextTimerAsync();
+ dataRetriever.mockRejectedValueOnce(error);
- expect(dataPromise).resolves.toStrictEqual({
+ expectedState = {
data: null,
error,
isLoading: false,
- });
- expect(dataPromise).resolves.toStrictEqual(get(dataStore));
-
+ };
dataPromise = dataStore.getData();
expect(get(dataStore)).toStrictEqual({
- data: null,
+ data: data1,
error: null,
isLoading: true,
});
await vi.advanceTimersToNextTimerAsync();
- expect(dataPromise).resolves.toStrictEqual({
- data,
+ expect(await dataPromise).toStrictEqual(expectedState);
+ expect(get(dataStore)).toStrictEqual(expectedState);
+ });
+
+ it("should expose a `reset` method to reset the data to its initial state", async () => {
+ expect(get(dataStore)).toStrictEqual({
+ data: null,
error: null,
isLoading: false,
});
- expect(dataPromise).resolves.toStrictEqual(get(dataStore));
- dataRetriever.mockRejectedValueOnce(error);
+ await dataStore.getData(...args);
- dataPromise = dataStore.getData();
+ expect(get(dataStore)).toStrictEqual({
+ data: data1,
+ error: null,
+ isLoading: false,
+ });
+
+ dataStore.reset();
expect(get(dataStore)).toStrictEqual({
- data,
+ data: null,
error: null,
- isLoading: true,
+ isLoading: false,
});
- await vi.advanceTimersToNextTimerAsync();
+ dataRetriever.mockRejectedValueOnce(error);
+
+ await dataStore.getData(...args);
- expect(dataPromise).resolves.toStrictEqual({
+ expect(get(dataStore)).toStrictEqual({
data: null,
error,
isLoading: false,
});
- expect(dataPromise).resolves.toStrictEqual(get(dataStore));
+
+ dataStore.reset();
+
+ expect(get(dataStore)).toStrictEqual({
+ data: null,
+ error: null,
+ isLoading: false,
+ });
+ });
+
+ it("should ignore the pending promise when `reset` is called and have `getData` return the reset store", async () => {
+ const expectedInitialState = {
+ data: null,
+ error: null,
+ isLoading: false,
+ };
+
+ await dataStore.getData(...args);
+
+ let dataPromise = dataStore.getData(...args);
+
+ expect(get(dataStore)).toStrictEqual({
+ data: data1,
+ error: null,
+ isLoading: true,
+ });
+
+ dataStore.reset();
+
+ expect(await dataPromise).toStrictEqual(expectedInitialState);
+ expect(get(dataStore)).toStrictEqual(expectedInitialState);
+
+ await dataStore.getData(...args);
+
+ dataRetriever.mockRejectedValueOnce(error);
+ dataPromise = dataStore.getData(...args);
+
+ expect(get(dataStore)).toStrictEqual({
+ data: data1,
+ error: null,
+ isLoading: true,
+ });
+
+ dataStore.reset();
+
+ expect(await dataPromise).toStrictEqual(expectedInitialState);
+ expect(get(dataStore)).toStrictEqual(expectedInitialState);
+ });
+
+ it("should ignore the pending promise when `reset` is called and a `getData` is called immediately afterwards and return the new result", async () => {
+ const expectedState = {
+ data: data2,
+ error: null,
+ isLoading: false,
+ };
+
+ dataRetriever
+ .mockImplementationOnce(() => resolveAfter(1000, data1))
+ .mockResolvedValueOnce(data2);
+
+ dataStore.getData(...args);
+ dataStore.reset();
+
+ const dataPromise = dataStore.getData(...args);
+
+ expect(await dataPromise).toStrictEqual(expectedState);
+ expect(get(dataStore)).toStrictEqual(expectedState);
+
+ // waiting for the first promise to resolve
+ await vi.advanceTimersToNextTimerAsync();
+
+ expect(get(dataStore)).toStrictEqual(expectedState);
});
});
diff --git a/explorer/src/lib/dusk/svelte-stores/__tests__/createPollingDataStore.spec.js b/explorer/src/lib/dusk/svelte-stores/__tests__/createPollingDataStore.spec.js
index 242ae10b50..10d6deb61d 100644
--- a/explorer/src/lib/dusk/svelte-stores/__tests__/createPollingDataStore.spec.js
+++ b/explorer/src/lib/dusk/svelte-stores/__tests__/createPollingDataStore.spec.js
@@ -27,8 +27,9 @@ describe("createPollingDataStore", () => {
vi.useRealTimers();
});
- it("should create a readable data store and expose `start` and `stop` as service methods", () => {
+ it("should create a readable data store and expose `reset`, `start` and `stop` as service methods", () => {
expect(dataRetriever).not.toHaveBeenCalled();
+ expect(pollingDataStore).toHaveProperty("reset", expect.any(Function));
expect(pollingDataStore).toHaveProperty("start", expect.any(Function));
expect(pollingDataStore).toHaveProperty("stop", expect.any(Function));
expect(pollingDataStore).toHaveProperty("subscribe", expect.any(Function));
@@ -160,35 +161,188 @@ describe("createPollingDataStore", () => {
isLoading: false,
});
- vi.advanceTimersByTime(fetchInterval - 1);
- await tick();
+ await vi.advanceTimersByTimeAsync(fetchInterval * 10);
expect(dataRetriever).toHaveBeenCalledTimes(2);
+ expect(get(pollingDataStore)).toStrictEqual({
+ data: null,
+ error,
+ isLoading: false,
+ });
});
- it("should not make a new call when the `start` method is invoked and the polling is already running", async () => {
+ it("should be able to restart polling after an error", async () => {
+ dataRetriever.mockResolvedValueOnce(data1).mockRejectedValueOnce(error);
+
pollingDataStore.start(...args);
+
+ await vi.advanceTimersToNextTimerAsync();
+
+ expect(dataRetriever).toHaveBeenCalledTimes(2);
+ expect(get(pollingDataStore)).toStrictEqual({
+ data: null,
+ error,
+ isLoading: false,
+ });
+
+ await vi.advanceTimersByTimeAsync(fetchInterval * 10);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(2);
+
pollingDataStore.start(...args);
+ await vi.advanceTimersByTimeAsync(fetchInterval * 10);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(13);
+
+ pollingDataStore.stop();
+ });
+
+ it("should start a new poll process and stop the previous one when the `start` method is called and a polling is running", async () => {
+ dataRetriever.mockImplementation((v) =>
+ Promise.resolve(v === 1 ? data1 : data2)
+ );
+
+ pollingDataStore.start(1);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(1);
+ expect(dataRetriever).toHaveBeenNthCalledWith(1, 1);
expect(get(pollingDataStore)).toStrictEqual({
data: null,
error: null,
isLoading: true,
});
+ pollingDataStore.start(2);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(2);
+ expect(dataRetriever).toHaveBeenNthCalledWith(2, 2);
+
await vi.advanceTimersByTimeAsync(1);
+ expect(dataRetriever).toHaveBeenCalledTimes(2);
+ expect(get(pollingDataStore)).toStrictEqual({
+ data: data2,
+ error: null,
+ isLoading: false,
+ });
+
+ await vi.advanceTimersByTimeAsync(fetchInterval * 10);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(12);
+
+ for (let i = 3; i <= 12; i++) {
+ expect(dataRetriever).toHaveBeenNthCalledWith(i, 2);
+ }
+
+ pollingDataStore.stop();
+ dataRetriever.mockResolvedValue(data1);
+ });
+
+ it("should expose a `reset` method that stops the polling and resets the store to its initial state", async () => {
+ const expectedInitialState = {
+ data: null,
+ error: null,
+ isLoading: false,
+ };
+
+ expect(get(pollingDataStore)).toStrictEqual(expectedInitialState);
+
+ dataRetriever.mockResolvedValueOnce(data1).mockResolvedValueOnce(data2);
+
+ pollingDataStore.start(...args);
+
+ await vi.advanceTimersByTimeAsync(fetchInterval - 1);
+
expect(get(pollingDataStore)).toStrictEqual({
data: data1,
error: null,
isLoading: false,
});
+ expect(dataRetriever).toHaveBeenCalledTimes(1);
+
+ pollingDataStore.reset();
+
+ expect(get(pollingDataStore)).toStrictEqual(expectedInitialState);
+
+ await vi.advanceTimersByTimeAsync(fetchInterval * 10);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(1);
+ expect(get(pollingDataStore)).toStrictEqual(expectedInitialState);
+
+ dataRetriever.mockRejectedValueOnce(error).mockResolvedValueOnce(data1);
+
pollingDataStore.start(...args);
+
+ await vi.advanceTimersByTimeAsync(fetchInterval - 1);
+
+ expect(get(pollingDataStore)).toStrictEqual({
+ data: data2,
+ error: null,
+ isLoading: false,
+ });
+
+ await vi.advanceTimersByTimeAsync(fetchInterval - 1);
+
+ expect(get(pollingDataStore)).toStrictEqual({
+ data: null,
+ error,
+ isLoading: false,
+ });
+
+ expect(dataRetriever).toHaveBeenCalledTimes(3);
+
+ pollingDataStore.reset();
+
+ expect(get(pollingDataStore)).toStrictEqual(expectedInitialState);
+
+ await vi.advanceTimersByTimeAsync(fetchInterval * 10);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(3);
+ expect(get(pollingDataStore)).toStrictEqual(expectedInitialState);
+ });
+
+ it("should be able to restart a polling after a `reset`", async () => {
pollingDataStore.start(...args);
- expect(dataRetriever).toHaveBeenCalledTimes(1);
- expect(dataRetriever).toHaveBeenCalledWith(...args);
+ await vi.advanceTimersByTimeAsync(fetchInterval * 10);
+
+ expect(dataRetriever).toHaveBeenCalledTimes(11);
+
+ pollingDataStore.reset();
+ expect(get(pollingDataStore)).toStrictEqual({
+ data: null,
+ error: null,
+ isLoading: false,
+ });
+
+ await vi.advanceTimersByTimeAsync(fetchInterval * 10);
+
+ expect(get(pollingDataStore)).toStrictEqual({
+ data: null,
+ error: null,
+ isLoading: false,
+ });
+
+ dataRetriever.mockResolvedValueOnce(data2).mockResolvedValueOnce(data3);
+ pollingDataStore.start(...args);
+
+ await vi.advanceTimersByTimeAsync(fetchInterval - 1);
+
+ expect(get(pollingDataStore)).toStrictEqual({
+ data: data2,
+ error: null,
+ isLoading: false,
+ });
+
+ await vi.advanceTimersByTimeAsync(fetchInterval - 1);
+
+ expect(get(pollingDataStore)).toStrictEqual({
+ data: data3,
+ error: null,
+ isLoading: false,
+ });
pollingDataStore.stop();
});
diff --git a/explorer/src/lib/dusk/svelte-stores/createDataStore.js b/explorer/src/lib/dusk/svelte-stores/createDataStore.js
index 733f1dbf9a..a8ea73d720 100644
--- a/explorer/src/lib/dusk/svelte-stores/createDataStore.js
+++ b/explorer/src/lib/dusk/svelte-stores/createDataStore.js
@@ -1,4 +1,4 @@
-import { writable } from "svelte/store";
+import { get, writable } from "svelte/store";
import { getErrorFrom } from "$lib/dusk/error";
@@ -17,48 +17,60 @@ function createDataStore(dataRetriever) {
const dataStore = writable(initialState);
const { set, subscribe, update } = dataStore;
- /** @type {ReturnType | null} */
- let dataPromise = null;
+ /** @type {number} */
+ let currentRetrieveId = 0;
/** @type {(...args: Parameters) => Promise} */
const getData = (...args) => {
- if (dataPromise) {
- return dataPromise;
- }
+ const retrieveId = ++currentRetrieveId;
update((store) => ({ ...store, error: null, isLoading: true }));
- dataPromise = dataRetriever(...args)
+ return dataRetriever(...args)
.then((data) => {
- const newStore = { data, error: null, isLoading: false };
+ if (retrieveId === currentRetrieveId) {
+ const newStoreContent = { data, error: null, isLoading: false };
- set(newStore);
+ set(newStoreContent);
- return newStore;
+ return newStoreContent;
+ } else {
+ return get(dataStore);
+ }
})
.catch(
/** @param {any} error */
(error) => {
- const newStore = {
- data: null,
- error: getErrorFrom(error),
- isLoading: false,
- };
+ if (retrieveId === currentRetrieveId) {
+ const newStoreContent = {
+ data: null,
+ error: getErrorFrom(error),
+ isLoading: false,
+ };
- set(newStore);
+ set(newStoreContent);
- return newStore;
+ return newStoreContent;
+ } else {
+ return get(dataStore);
+ }
}
- )
- .finally(() => {
- dataPromise = null;
- });
+ );
+ };
- return dataPromise;
+ const reset = () => {
+ /**
+ * We don't want pending promises to be written
+ * in the store, and we don't want id clashes
+ * if `getData` is called immediately after `reset`.
+ */
+ currentRetrieveId++;
+ set(initialState);
};
return {
getData,
+ reset,
subscribe,
};
}
diff --git a/explorer/src/lib/dusk/svelte-stores/createPollingDataStore.js b/explorer/src/lib/dusk/svelte-stores/createPollingDataStore.js
index 74d400b17a..0d6dca90b8 100644
--- a/explorer/src/lib/dusk/svelte-stores/createPollingDataStore.js
+++ b/explorer/src/lib/dusk/svelte-stores/createPollingDataStore.js
@@ -1,5 +1,4 @@
import { derived, get } from "svelte/store";
-import { isNull, keySatisfies, when } from "lamb";
import { resolveAfter } from "$lib/dusk/promise";
@@ -11,37 +10,39 @@ import { createDataStore } from ".";
* @returns {PollingDataStore}
*/
const createPollingDataStore = (dataRetriever, fetchInterval) => {
- /** @type {boolean} */
- let isPolling = false;
+ /** @type {number} */
+ let currentPollId = 0;
const dataStore = createDataStore(dataRetriever);
- /** @type {(...args: any) => void} */
- const poll = (...args) => {
- if (isPolling) {
+ /** @type {(pollId: number, args: Parameters) => void} */
+ const poll = (pollId, args) => {
+ if (pollId === currentPollId) {
dataStore
.getData(...args)
- .then(
- when(keySatisfies(isNull, "error"), () =>
- resolveAfter(fetchInterval, undefined).then(() => poll(...args))
- )
+ .then((store) =>
+ store.error === null
+ ? resolveAfter(fetchInterval, undefined).then(() =>
+ poll(pollId, args)
+ )
+ : stop()
)
.catch(stop);
}
};
+ const reset = () => {
+ stop();
+ dataStore.reset();
+ };
+
const stop = () => {
- isPolling = false;
+ currentPollId++;
};
- /** @type {(...args: any) => void} */
+ /** @type {(...args: Parameters) => void} */
const start = (...args) => {
- if (isPolling) {
- return;
- }
-
- isPolling = true;
- poll(...args);
+ poll(++currentPollId, args);
};
const pollingDataStore = derived(
@@ -53,6 +54,7 @@ const createPollingDataStore = (dataRetriever, fetchInterval) => {
);
return {
+ reset,
start,
stop,
subscribe: pollingDataStore.subscribe,
diff --git a/explorer/src/lib/dusk/svelte-stores/svelte-stores.d.ts b/explorer/src/lib/dusk/svelte-stores/svelte-stores.d.ts
index 1ad17b3608..1a60944ee4 100644
--- a/explorer/src/lib/dusk/svelte-stores/svelte-stores.d.ts
+++ b/explorer/src/lib/dusk/svelte-stores/svelte-stores.d.ts
@@ -8,9 +8,11 @@ type DataStoreContent = {
type DataStore = Readable & {
getData: (...args: any) => Promise;
+ reset: () => void;
};
type PollingDataStore = Readable & {
+ reset: () => void;
start: (...args: any) => void;
stop: (...args: any) => void;
};