Reputation: 48602
I am working on app where I need to implement offline architecture. My implementation is I am saving the requests whatever user is performing like update order status, update delivery status.
It has to be sequentially like Req1(Shipped)->Req2(Out For Delivery) -> Req3(Delivered) I have a method (uploadRequests ) where I am doing sync with the server. Whenever internet is back, If user refresh the order list by swipe up then I am uploading the offline data then calling order list api. It is like this.
OrderList.js
<List onRefresh=(() => { uploadRequests()) />
offline-request.js
export async function uploadRequests() {
for (let index = 0; index < sortedRequests.length; index++) {
const keyName = "REQUEST_" + sortedRequests[index];
const { consigmentID, userID, type, request, sync } = await load(keyName);
// Update sync request
const payload = {
type: TYPE.UPDATE_LINE_ITEM,
request: request,
sync: SYNC.IN_PROCESS,
userID: userID,
consigmentID: consigmentID,
};
await save(keyName, payload);
// update sync status to SYNC.IN_PROCESS in the storage
if (sync === SYNC.TO_BE_UPLOAD) {
// make API call to upload this request to server
}
// delete the request from storage once api hit is success
await remove(keyName);
}
}
Problem-
Here initially all offline payload sync
status is SYNC.TO_BE_UPLOAD, once sync start for this request, then I am updating the status to sync
to SYNC.IN_PROCESS one by one after request has been hit to server so that if user swipe multiple times then this request wont be picked up.
So if I saved 30 requests in the storage to sync with server. 1st time if user swipe up and internet is back then suppose it has processed 1st to 5th requests and set the sync status to IN_PROCESS so other thread wont process it and if meanwhile user swipe up list again then this uploadRequests
method will call again. Here it will pick up 6th to 30th requests and first thread also it will process 6th to 30th requests so both threads will process 6th to 30th requests twice times.
How I can handle this problem gracefully in javascript without variable ? Main thing all other areas where I am calling API has to wait first to clear this storage.
Problem with variable is Suppose I am on order list page, i swipe up and it start uploading requests, if swipe up then i can ignore this api hit using variable but If i tap on one order and mark it delivered then it will also skip offline requests and make delivered api call but here i want offline requests should clear from storage then make new call when internet is available. This is the reason I don't want using variable like isOfflineRunning=true/false.
Any suggestions to solve this problem ?
Upvotes: 0
Views: 162
Reputation: 2029
You can wrap your uploadRequests
function with this singleCallOnly
function like
const singleCallOnly = fn => {
let lastPromise = null;
return async (...args) => {
if(lastPromise) await lastPromise;
lastPromise = fn(...args);
return lastPromise;
}
}
export const uploadRequests = singleCallOnly(_uploadRequests)
You can run the attached snippet for a sample response.
However, on a sidenote, I'm doubtful about the way you're consuming sortedRequests
. This way, you should ensure that no element is added/removed from sortedRequests
while that function is running.
If sortedRequests
is like a queue of pending requests, you should consume it like
while(sortedRequests.length) {
const key = sortedRequests.shift();
const keyName = "REQUEST_" + key;
// ... rest of code
}
// Helper functions start
const sleep = async (ms) => new Promise((res) => setTimeout(() => res(ms), ms));
const simulateLatency = () => sleep(50 + Math.floor(Math.random() * 500));
const SYNC = {
TO_BE_UPLOAD: "TO_BE_UPLOAD",
IN_PROCESS: "IN_PROCESS",
};
const TYPE = {
UPDATE_LINE_ITEM: "UPDATE_LINE_ITEM",
};
const requests = Array(10)
.fill()
.map((_, i) => ({
consigmentID: `c${i}`,
userID: `u${i}`,
type: TYPE.UPDATE_LINE_ITEM,
request: "some-request",
sync: SYNC.TO_BE_UPLOAD,
key: `REQUEST_K${i}`,
}));
const load = async (key) => {
// simulate latency
await simulateLatency();
return requests.find((r) => r.key === key);
};
const save = async (keyname, payload) => {
await simulateLatency();
requests.find((r) => r.key === keyname).sync = SYNC.IN_PROCESS;
};
const remove = async (keyname) => {
await simulateLatency();
const idx = requests.findIndex((r) => r.key === keyname);
if (idx < 0) return;
requests.splice(idx, 1);
};
const sortedRequests = Array(5)
.fill()
.map((_, i) => `K${i}`);
// Helper functions end
// YOUR CODE STARTS
async function _uploadRequests() {
while(sortedRequests.length) {
const key = sortedRequests.shift()
const keyName = "REQUEST_" + key;
console.log('start', keyName);
const { consigmentID, userID, type, request, sync } = await load(keyName);
// Update sync request
const payload = {
type: TYPE.UPDATE_LINE_ITEM,
request: request,
sync: SYNC.IN_PROCESS,
userID: userID,
consigmentID: consigmentID,
};
await save(keyName, payload);
// update sync status to SYNC.IN_PROCESS in the storage
if (sync === SYNC.TO_BE_UPLOAD) {
// make API call to upload this request to server
}
// delete the request from storage once api hit is success
await remove(keyName);
console.log('done', keyName);
}
}
const singleCallOnly = fn => {
let lastPromise = null;
return async (...args) => {
if(lastPromise) await lastPromise;
lastPromise = fn(...args);
return lastPromise;
}
}
(async () => {
const uploadRequests = singleCallOnly(_uploadRequests);
uploadRequests();
await sleep(1000);
uploadRequests();
await sleep(1000);
uploadRequests();
})();
Upvotes: 1