EDIT: I ve updated this question, and the old question is moved here.
A POC can be found on this jsfiddle or the snippet below
GOAL:
The goal here is to make or rather simulate a promise that can be updated, whose lifecycle can be essentially reset.
Optionally, I wanted more control over promise resolution using inversion of control, like this:
const ex = {};
const prom = new Promise<T>((resolve, reject) => {
ex.resolve = resolve;
ex.reject = reject;
});
IMPLEMENTATION:
Snippet:
You can type in the new value, and choose whether to reject or resolve the promise. Rejected values will be highlighted.
By default the promise is rejected with a value
default
.
Edit: By default, there s a .then()
callback attached (line 85 in snippet). So once you resolve or reject with a value for the first time, you ll see your value printed twice, which is the expected behavior.
class UpdatablePromise {
/**
* @param prom - the default promise object
* @param executor - optional ExternalExecutor argument to store executor internally.
* @returns UpdatablePromise
*/
constructor(prom, executor) {
this.value = undefined;
this.executors = [];
if (executor) this.executors.push(Object.assign({}, executor));
/**
* Call previous resolve/reject functions to fulfil previous promises, if any.
* helper function, just to reduce code duplication in `update` function.
*/
const prevResolvers = (type) => {
return (val) => {
const flag = type === "reject";
while (true) {
const executor = this.executors.shift();
if (executor) flag ? executor.reject(val) : executor.resolve(val);
else break;
}
return val;
};
};
const handler = {
get: (target, prop) => {
// console.log(prop);
if (prop === "update") {
// return a function which takes the new promise value and its executor as argument
return (v, executor) => {
// console.log( upd , v, this.value);
this.value = v;
if (executor) this.executors.push(executor);
// attach `then` function to the new promise, so that the old promises are fulfilled when the new one does
// this has no effect on already fulfilled promises.
v.then(
(val) => prevResolvers("resolve")(val),
(val) => prevResolvers("reject")(val)
);
};
} else if (typeof this.value === "undefined") {
// if value is undefined, i.e. promise was never updated, return default property values
return typeof target[prop] === "function" ?
target[prop].bind(target) :
target[prop];
}
// else, attach functions to new promise
else if (prop === "then") {
// console.log( then , this.value);
return (onfulfilled, onrejected) =>
this.value.then(onfulfilled, onrejected);
} else if (prop === "catch") {
return (onrejected) => this.value.catch(onrejected);
} else if (prop === "finally") {
return (onfinally) => this.value.finally(onfinally);
} else return target[prop];
},
};
return new Proxy(prom, handler);
}
}
const input = document.getElementById("input")
const output = document.getElementById("output")
const resBtn = document.getElementById("res")
const rejBtn = document.getElementById("rej")
const ex_1 = {
resolve: () => void 0,
reject: () => void 0,
};
const prom_1 = new Promise((resolve, reject) => {
ex_1.resolve = resolve;
ex_1.reject = reject;
});
const up = new UpdatablePromise(prom_1, ex_1)
// Await the promise
up.then(_ => print(_), _ => print(_))
// Print the value of promise, highlight if it is rejected
async function print() {
try {
const val = await up;
output.innerHTML += `
${val}`
} catch (e) {
output.innerHTML += `
<mark>${e}</mark>`
}
}
resBtn.addEventListener("click", () => {
up.update(Promise.resolve(input.value));
print();
})
rejBtn.addEventListener("click", () => {
up.update(Promise.reject(input.value));
print();
})
function consoleTest() {
const ex = {
resolve: () => void 0,
reject: () => void 0,
};
const prom = new Promise((resolve, reject) => {
ex.resolve = resolve;
ex.reject = reject;
});
const up = new UpdatablePromise(prom, ex);
setTimeout(() => ex.resolve("resolved"), 1000);
up.then(
(_) => console.log(_, "res"),
(_) => console.log(_, "rej")
);
setTimeout(async() => {
up.update(Promise.reject("reject"));
up.then(
(_) => console.log(_, "res"),
(_) => console.log(_, "rej")
);
}, 2000);
setTimeout(async() => {
up.update(Promise.resolve("res again"));
up.then(
(_) => console.log(_, "res"),
(_) => console.log(_, "rej")
);
}, 2500);
}
consoleTest();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<label for="input">Enter a value to update promise with:</label>
<input id="input" />
<div>
<button id="res">
resolve
</button>
<button id="rej">
reject
</button>
</div>
<pre id="output"></pre>
</body>
</html>
ts playground - ts version (may not be updated. Prefer fiddle version)
Here s the part that prints the promise s value:
// Print the value of promise, highlight if it is rejected
async function print() {
try {
const val = await up;
output.innerHTML += `
${val}`
} catch (e) {
output.innerHTML += `
<mark>${e}</mark>`
}
}
PROBLEM:
It seems to work sometimes, for example, in console or in the fiddle.
But doesn t actually work when I use it in the service worker, only the old value of the promise is returned, . I m using workbox, if that s relevant.
I m yet to pinpoint the problem. So here s the actual workbox code that doesn t work for me:
// If the refresh Token is resolved, the user is logged in, otherwise not.
class MyStrat extends Strategy {
async _handle() {
try {
await refreshToken;
return new Response(JSON.stringify({ is_logged_in: true }), {
status: 200,
headers: [[CONTENT_TYPE, APPLICATION_JSON]],
});
} catch (e) {
console.log( error , e);
return new Response(JSON.stringify({ is_logged_in: false }), {
status: 400,
headers: [[CONTENT_TYPE, APPLICATION_JSON]],
});
}
}
}
PRECISE QUESTION:
I d like to know a few things:
Does it really work as mentioned above? (i.e. It works exactly like a promise, with the exception that it can be "reset" and once it is updated, all pending
await
s or attached callbacks fulfil with the new updated value)If yes, then what could be the reason that it doesn t work in (workbox s) service worker.
If no, then what s actually happening?
If it really does work, then is it possible to add a
state
member variable to synchronously keep track of promise state? (I tried it once, didn t work at all)
Edit 2:
Apparently this actually does seem to work in service worker as well. The promise was getting updated to value stored in database repeatedly due to some other functions.
However, I d still like to get an answer for my other questions