It might sound weird, but I'm looking for a way to resolve a promise multiple times. Are there any approaches to make this possible?
Think of the following example:
getPromise() {
const event = new Event('myEvent');
setTimeout(() => {
window.dispatchEvent(event);
}, 5000);
setTimeout(() => {
window.dispatchEvent(event);
}, 7000);
return new Promise((resolve) => {
window.addEventListener('myEvent', () => {
resolve('some value'));
});
resolve('some value'));
});
};
And then .then():
getPromise().then(data => {console.log(data)})
Should give the following result:
some value // initial
some value // after 5000ms
some value // after 7000ms
So I know there are libraries to stream data, but I'm really looking for a native non-callbak approach to achieve this.
How to resolve a promise multiple times?
You can't. Promises can only be resolved once. Once they have been resolved, they never ever change their state again. They are essentially one-way state machines with three possible states pending, fulfilled and rejected. Once they've gone from pending to fulfilled or from pending to rejected, they cannot be changed.
So, you pretty much cannot and should not be using promises for something that you want to occur multiple times. Event listeners or observers are a much better match than promises for something like that. Your promise will only ever notify you about the first event it receives.
I don't know why you're trying to avoid callbacks in this case. Promises use callbacks too in their .then() handlers. You will need a callback somewhere to make your solution work. Can you explain why you don't just use window.addEventListener('myEvent', someCallback) directly since that will do what you want?
You could return a promise-like interface (that does not follow Promise standards) that does call its notification callbacks more than once. To avoid confusion with promises, I would not use .then() as the method name:
function getNotifier() {
const event = new Event('myEvent');
setTimeout(() => {
window.dispatchEvent(event);
}, 500);
setTimeout(() => {
window.dispatchEvent(event);
}, 700);
let callbackList = [];
const notifier = {
notify: function(fn) {
callbackList.push(fn);
}
};
window.addEventListener('myEvent', (data) => {
// call all registered callbacks
for (let cb of callbackList) {
cb(data);
}
});
return notifier;
};
// Usage:
getNotifier().notify(data => {console.log(data.type)})
I have a solution in Typescript.
export class PromiseParty {
private promise: Promise<string>;
private resolver: (value?: string | PromiseLike<string>) => void;
public getPromise(): Promise<string> {
if (!this.promise) {
this.promise = new Promise((newResolver) => { this.resolver = newResolver; });
}
return this.promise;
}
public setPromise(value: string) {
if(this.resolver) {
this.resolver(value);
this.promise = null;
this.resolver = null;
}
}
}
export class UseThePromise {
public constructor(
private promiseParty: PromiseParty
){
this.init();
}
private async init(){
const subscribe = () => {
const result = await this.promiseParty.getPromise();
console.log(result);
subscribe(); //resubscribe!!
}
subscribe(); //To start the subscribe the first time
}
}
export class FeedThePromise {
public constructor(
private promiseParty: PromiseParty
){
setTimeout(() => {
this.promiseParty.setPromise("Hello");
}, 1000);
setTimeout(() => {
this.promiseParty.setPromise("Hello again!");
}, 2000);
setTimeout(() => {
this.promiseParty.setPromise("Hello again and again!");
}, 3000);
}
}
Related
I want to be able to cancel a started promise from my Vue component, specifically a promise returned by a Vuex action.
My use case is that my Vuex action polls an endpoint for status, and I want to be able to cancel that polling if the user performs a certain action (close function in the example).
I have created a custom CancellablePromise class lifted from another stackoverflow answer, but it isn't working with Vuex.
Cancelable promise class (from https://stackoverflow.com/a/60600274/2152511)
export class CancellablePromise<T> extends Promise<T> {
private onCancel: () => void;
constructor(
executor: (
resolve: (value?: T | PromiseLike<T>) => void,
reject: (reason?: any) => void,
onCancel: (cancelHandler: () => void) => void
) => void
) {
let onCancel: () => void;
super((rs, rj) =>
executor(rs, rj, (ch: () => void) => {
onCancel = ch;
})
);
this.onCancel = onCancel;
}
public cancel(): void {
if (this.onCancel) {
this.onCancel();
}
}
}
Action
async [SomeAction.foo]({ state, dispatch, commit, rootGetters }) {
const cancellablePromise = new CancellablePromise<any>((resolve, reject, onCancel) => {
const interval = setInterval(async () => {
const status = await dispatch(SomeAction.bar);
if (status === "goodstatus") {
clearInterval(interval);
resolve();
} else if (status === "badstatus") {
clearInterval(interval);
reject();
}
}, 2000);
onCancel(() => {
clearInterval(interval);
reject();
});
});
return cancellablePromise;
}
Component
data: (() => {
promise: undefined as CancellablePromise<any> | undefined
}),
async call() {
this.promise = this.$store
.dispatch(SomeAction.foo)
.then(response => {
// do something
}) as CancellablePromise<any>;
},
close(): void {
if (this.promise) {
this.promise.cancel(); // outputs cancel is not a function
}
}
The problem occurs in the close function where this.promise.cancel is not a function.
This seems to me is because the object returned by dispatch is indeed a Promise, not a CancellablePromise. My suspicion comes from looking at the Vuex source which, again, seems to create a new Promise from the Promise returned from the action. I'm not very familiar with Typescript's type system, but unless I'm misreading this code I think my CancellablePromise is "lost" here.
How can I accomplish what I want to do here?
Extentding Promise is messy and unnecessary. It's more normal
to expose a Promise's reject method to the wider world (outside the Promise's constructor), and call it wherever necessary to cause the Promise to adopt its error path.
to race a "cancellation Promise" against the Promise of interest but that's not necessary here as the promisification of the setInterval process makes a reject method available.
Something like this should do it (untested).
Action
async [SomeAction.foo]({ state, dispatch, commit, rootGetters }) {
let reject_, interval;
const promise = new Promise((resolve, reject) => {
reject_ = reject; // externalise the reject method
interval = setInterval(async () => {
const status = await dispatch(SomeAction.bar);
if (status === 'goodstatus') {
resolve();
} else if (status === 'badstatus') {
reject(new Error(status)); // for example
} else {
// ignore other states ???
}
}, 2000);
});
promise.cancel = reject_; // decorate promise with its own reject method.
return promise.always(() => { clearInterval(interval) }); // clear the interval however the promise settles (resolve() or reject() above, or promise.cancel() externally).
}
Component
data: (() => {
cancel: null
}),
async call() {
this.close(new Error('new call was made before previous call completed')); // may be a good idea
let promise = this.$store.dispatch(SomeAction.foo); // don't chain .then() yet otherwise the .cancel property is lost.
this.cancel = promise.cancel; // store the means to force-reject the promise;
return promise.then(response => { // now chain .then()
// do something
})
.catch(error => {
console.log(error);
throw error;
});
},
close(reason): void {
if (this.cancel) {
this.cancel(reason || new Error('cancelled'));
}
}
I have the following function, which is meant to allow me to wait for a particular condition to obtain in my redux state.
async function when(store, condition) {
if (condition(store.getState())) {
return;
} else {
return new Promise(resolve => {
const unsubscribe = store.subscribe(() => {
if (condition(store.getState())) {
unsubscribe();
resolve();
}
});
});
}
}
However, I'm struggling to work out if this is free of race conditions. In particular, if I have a function like...:
async function foo(store) {
await when(store, thereAreExactlyThreeTodoItems);
doSomethingThatRequiresExactlyThreeTodoItems();
}
...am I guaranteed that the condition represented by thereAreExactlyThreeTodoItems() is true when doSomethingThatRequiresExactlyThreeTodoItems() is called? Or does this need a further step to guarantee? i.e.:
async function foo(store) {
do {
await when(store, thereAreExactlyThreeTodoItems);
} while (!thereAreExactlyThreeTodoItems(store.getState());
doSomethingThatRequiresExactlyThreeTodoItems();
}
The worry I have in mind is: could an action be dispatched that invalidates the condition after resolve() is called, but before control is returned to foo()? Unfortunately, I don't have quite a good enough mental model of the javascript event loop to be sure.
Any help much appreciated.
It looks good what you are trying to do, the only thing which seems strange is that you do not cache the promise creation...
I think you have to create a map and cache the created promises so you do not create a new promise for the same functions.
In this case, I don't see a way in which you can have race conditions problems.
Something along this lines:
const cache = {}
function when(store, condition, id) {
if (condition(store.getState())) {
return;
} else if(!cache[id]) {
cache[id] = true;
return new Promise(resolve => {
const unsubscribe = store.subscribe(() => {
if (condition(store.getState())) {
unsubscribe();
resolve();
delete cache[id]
}
});
});
}
}
}
Here is my code:
private loadingData() {
var promise = new Promise<any>((resolve) =>
{
resolve();
});
promise.then(() => {
this.asyncServiceCall();
})
.then(() => this.syncFunctionThree())
};
The asynchronous method asyncServiceCall actually returns a Promise.
private asyncServiceCall(): Promise<any> {
return new Promise((resolve) = > {
resolve();
}).then(() => {
this.functionOne()
})
.then(() => {
this.functionTwo();
});
}
Okay, let's look at functionOne and functionTwo. They are both returning Promise.
private functionOne() {
return new Promise((resolve) => {
this.user['load'] = true;
this.service['user'].get().subscribe(d =>
{
this.user['d'] = d;
},
error => console.error(error),
() => {
this.role['load']=false;
});
resolve();
});
}
private functionTwo() {
return new Promise((resolve) => {
this.service['name'].get().subscribe(d =>
{
this.name['d'] = d;
},
resolve();
});
}
The third method syncFunctionThree will use the data this.user['d'] and this.name['d'] to have some business logic. So I want to call functionOne and functionTwo first then call syncFunctionThree.
By the accepted answer without creating new Promise, I don't get lucky. I found that the
syncFunctionThree was called before the asynchronous methods.
So help me.
You are missing an important part of calling promises inside then() ... you need to return those promises or the then() will resolve immediately and go to the next then() in the chain. That's why functionThree is firing before the asynchronous promise functions resolve
private loadingData() {
var promise = new Promise<any>((resolve) =>
{
resolve();
});
promise.then(() => {
// return the promise this function returns
return this.asyncServiceCall();
// ^^^^
})
// this then() won't fire until asyncServiceCall resolves in previous then()
.then((resultFromPriorThen) => this.syncFunctionThree())
}
Now you don't really need this first promise in loadingData() because you already have a promise to work with returned by asyncServiceCall() and can simplify it to:
private loadingData(): Promise<any> {
return this.asyncServiceCall().then(()=>this.syncFunctionThree());
}
Now to fix asyncServiceCall() the same way:
private asyncServiceCall(): Promise<any> {
return this.functionOne().then(()=>this.functionTwo());
}
Final note: Need to add a catch() in loadingData() in case one of the asynchronous operations has a problem
private functionOne() {
return new Promise((resolve) => {
this.service['user'].get().subscribe(resolve);
});
}
private functionTwo() {
return new Promise((resolve) => {
this.service['name'].get().subscribe(resolve);
});
}
private loadingData() {
Promise.all([functionOne(), functionTwo()]).then(([user, name]) => {
this.user['d'] = user;
this.name['d'] = name;
}).then(() => this.syncFunctionThree())
};
By looking at the method signature if the this.service['user'].get() is an rx observable. you can use this.service['user'].get().toPromise() to get the promise directly. if the this.service['user'].get() has multiple values try this.service['user'].get().pipe(first()) instead
This my road of growing up: callbacks, promises, async/await. I keep in mind a pattern - fluent builder that looks more to interpreter. I would like to mix async/await with interpreter. Is it possible? I feel that it is not possible. But I can define why exactly.
I have:
after(async () => {
await shellContainer.uninstall()
await shellContainer.dispose()
})
I am interesting in:
after(async () => {
await shellContainer
.uninstall()
.dispose()
})
Regards.
To separate concerns, you could introduce a dedicated builder which implements the fluent interface.
With that you only need to return a promise from the final build method, making things a lot simpler.
The following is a rather crude but functional example:
class ShellContainer
{
uninstall() {
return new Promise(resolve => {
setTimeout(() => {
console.log('uninstalled');
resolve();
}, 400 + Math.random() * 600);
});
}
dispose() {
return new Promise(resolve => {
setTimeout(() => {
console.log('disposed');
resolve();
}, 400 + Math.random() * 600);
});
}
}
class ShellContainerBuilder
{
container;
plan;
constructor() {
this.reset();
}
reset() {
this.container = new ShellContainer();
this.plan = () => Promise.resolve();
}
stage(op) {
this.plan = ((prev) => () => prev().then(op))(this.plan);
}
uninstall() {
this.stage(() => this.container.uninstall());
return this;
}
dispose() {
this.stage(() => this.container.dispose());
return this;
}
build() {
return this.plan().then(() => this.container);
}
}
(async () => {
console.log('starting w/o builder:');
const shellContainer1 = new ShellContainer();
await shellContainer1.uninstall();
await shellContainer1.dispose();
console.log('w/o builder done.');
console.log('starting w/ builder:');
const shellContainer2 = await (new ShellContainerBuilder()).uninstall().dispose().build();
console.log(shellContainer2);
console.log('w/ builder done.');
})();
If you change .uninstall to return the instance (shellContainer) while assigning its Promise to a property of the instance, and retrieving that Promise in the chained call, yes, it's possible:
class ShellContainer {
uninstall() {
// Chain or construct a Promise and assign the result to a property of the instance:
this.uninstProm = new Promise((resolve, reject) => {
// do stuff
});
return this;
}
dispose() {
return this.uninstProm.then(() => {
// do stuff
});
}
}
and then,
await shellContainer
.uninstall()
.dispose()
will resolve once dispose finishes.
Note that with this approach, calling uninstall by itself may result in unexpected behavior, since .uninsall will be returning the instance synchronously, rather than the Promise. You might consider an extra argument or something to indicate whether you want to chain the uninstall call with something else, or whether you want the Promise to be returned directly, perhaps something like
class ShellContainer {
uninstall(doChain) {
// Chain or construct a Promise and assign the result to a property of the instance:
this.uninstProm = new Promise((resolve, reject) => {
// do stuff
});
return doChain ? this : this.uninstProm;
}
dispose() {
return this.uninstProm.then(() => {
// do stuff
});
}
}
and
await shellContainer
.uninstall(true)
.dispose()
or just
await shellContainer.uninstall(); // no doChain argument
But if there's only going to be one Promise, there's not much need for await in many cases - it may not make the code clearer at all. For example
after(async () => {
await shellContainer
.uninstall()
.dispose()
})
is equivalent to
after(() => shellContainer
.uninstall()
.dispose()
);
The pattern - Interpreter that implements Fluent builder is not suitable for Async/Await. Because await is applied per an operation. There is not such syntax to process async in the middle of the chain. To handle async operations you should split chaining and wrap the operations by await operator like it is at the first code snippet.
I am currently looking to convert a library that uses Q.defer() for promise handling to use ES6 Promises. I understand the basics on how to convert Q.defer() to Promise, but every example I have ran into does not talk about the structure that I am running into where I need to convert class instance variables away from Q.defer() to Promises that do NOT resolve immediately. For example, take the following code.
import 'Q' from 'q';
class Service {
constructor() {
this.items = Q.defer();
// This would then make a call to some backend service... setTimeout to simulate.
setTimeout(() => {
this.items.resolve(['one', 'two', 'three']);
}, 1000);
}
getItems() {
return this.items.promise;
}
}
I would like to use this class like the following.
let service = new Service();
service.getItems().then((items) => {
console.log(items);
});
Currently, I am reading that you should use Promise.resolve() to create a similar structure, however, if I replace Q.defer() with Promise.resolve(), the promise resolves immediately with no items, which is not what I am wanting. Here is what I was thinking could be the replacement.
class Service {
constructor() {
this.items = Promise.resolve();
// This would then make a call to some backend service... setTimeout to simulate.
setTimeout(() => {
this.items.then(() => {
return ['one', 'two', 'three'];
});
}, 1000);
}
getItems() {
return this.items;
}
}
This doesn't work since the promise resolves immediately...
How do I convert the code above to use Promises?
The problem is that deferred is not part of the Promise specification and a bit of an anti-pattern so native Promises don't have the same concept.
This would work in native, but I'm not sure if you can wrap your methods like this.
class Service {
constructor() {
this.items = new Promise(deferred)
function deferred(resolve, reject) {
setTimeout(() => {
resolve(['one', 'two', 'three']);
}, 1000);
}
}
getItems() {
return this.items;
}
}
Another thing that can be used to solve this problem is to create a Polyfill of the Deferred class using the following code.
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
And this can now be used like so...
class Service {
constructor() {
this.items = new Deferred();
// This would then make a call to some backend service... setTimeout to simulate.
setTimeout(() => {
this.items.resolve(['one', 'two', 'three']);
}, 1000);
}
getItems() {
return this.items.promise;
}
}