Cancelable Promise loses cancel property when accessed in foreign class object - javascript

I'm having troubles cancelling promise from the context that is different from the one the promise was created in. For instance this is how i create a cancelable promise:
import PCancelable from 'p-cancelable';
class Common {
static runPromise(){
const fetchPromise = new PCancelable((resolve, reject, onCancel) => {
setTimeout(() => {
resolve({ ok: true, data: [1, 2, 3] });
}, 10000);
onCancel(() => {
console.log('Promise is being canceled');
});
});
console.log(fetchPromise) //PCancelable type
//this works and will cancel the promise
//fetchPromise.cancel();
return fetchPromise;
}
}
Then I'm trying to call this static method from a different class and do a cancel on it like this:
const promise = Common.runPromise().then().catch();
console.log(promise ) //Promise type - I expect it to be PCancelable
promise.cancel(); //outputs promise.cancel is not a function
when i run a check:
console.log(promise)
if returns instance of "Promise" class. instead of "PCancelable" as it would do if I log it in original(Common) class.
How do i cast an output of static function "runPromise" to a "PCancellable" so it works as i want to?

Trying the above code on - https://repl.it/#sunnykgupta/TryingpCancelable gives me the expected output.
You're modifying the promise object enroute between returning the PCancelable and calling .cancel on that.
Basically, using an object in a different class would not change its constructor unless done explicitly or implicitly in this case by the then you've used.

Related

How does using a Proxy in js making a Promise look synchronous actually work?

const handler: ProxyHandler<any> = {
get: (target: Promise<any>, prop: string, receiver: any) => {
return target.then((o) => {
return o[prop].apply(o);
});
},
};
return new Proxy(obj, handler);
So I have this code that I copied from a gist on the internet, and it seems to work. I understand how the trap works, but I don't get how the proxy makes a return of a promise act like a synchronous value. My colleague is afraid this code has a race condition. Are there any race conditions? what's actually happening under the hood? do I need to improve this code any to make it safe?
code is written in typescript and running on nodejs 10.
It doesn't look like this code makes access synchronous at all. It looks like it serves to make any methods on the promise payload available on the promise, but they will still have deferred execution when invoked.
For example, given the following API:
interface Bird {
speak(): void;
}
function getBird(): Promise<Bird> {
return new Promise((resolve) => {
setTimeout(() => resolve({ speak() { console.log('CAW'); } }, 1000);
});
}
function proxyPromise<T>(promise: Promise<T>) {
// Your promise proxying code here
}
The proxyPromise code would allow you to call methods on the proxied object, and defer them until the promise is resolved:
const proxiedBird = proxyPromise(getBird());
console.log("Fetching Bird")
proxiedBird.speak;
console.log("Told it to speak");
This code would execute fine, but it won't make the speak() operation run synchronously--it will still wait for the getBird() promise to resolve. So in output you would see:
Fetching Bird
Told it to Speak
CAW
The code snippet you have would do nothing to support fields or methods with parameters on the proxied object.
You could describe its safe behavior with some typescript typing:
type MethodProxy<T> = {
[idx in keyof T]: T[idx] extends () => infer U ? Promise<U> : never;
}
function proxyPromise<T>(promise: Promise<T>): MethodProxy<T> {
const handler: ProxyHandler<any> = {
get: (target: Promise<any>, prop: string, receiver: any) => {
return target.then((o) => {
return o[prop].apply(o);
});
},
};
return new Proxy(promise, handler);
}
Tricky question #xenoterracide. But answering you, actually, this doesn't work synchronously at all, basically, you turned every obj property to be accessed only asynchronously, and if the property is not a function, it throws an error.
Under the hood, you are only trapping a promise get properties, and in the trap, waiting for the promise to resolve and execute the property (function) in this value resolved by the promise.
I simulate it in this playground:
const handler: ProxyHandler<any> = {
get: (target: Promise<any>, prop: string, receiver: any) => {
return target.then((o) => {
return o[prop].apply(o);
});
},
};
const obj = {
a() {
return 'Hi'
},
b: 5
}
const proxy = new Proxy(Promise.resolve(obj), handler);
//both prints promises
console.log(proxy.a) // after resolved, return 'Hi'
console.log(proxy.b) // error
This proxy approach could be useful if you don't want to resolve the promise itself. But you would need to await every property, and take care of not function ones.

Extending `Promise` and change `then` signature

I want to extend Promise and change the then signature so its callback receives two values. I tried different approaches two of which are documented and tested here. Sadly, I get various errors or the resulting class does not behave like a Promise.
Approach 1: Wrapping a native Promise
export class MyWrappedPromise {
constructor(data) {
this.data = data;
this.promise = new Promise(evaluate.bind(data));
}
then(callback) {
this.promise.then(() => callback(this.data, ADDITIONAL_DATA));
}
catch(callback) {
this.promise.catch(callback);
}
}
Approach 2: Extending native Promises
export class MyExtendedPromise extends Promise {
constructor(executor, data) {
super(executor);
this.data = data;
}
static create(data) {
return new MyExtendedPromise(evaluate.bind(data), data);
}
then(callback) {
return super.then(() => callback(this.data, ADDITIONAL_DATA));
}
}
Does anyone have any suggestion on what I am doing wrong? Feel free to create a PR on GitHub.
thanks
------------------- Edit ---------------------
Some Additional code and info to make the code above more understandable without looking at the code and tests on Github.
evaluate is just the Promise executor function. I extracted it out so I can keep it consistent across all my implementations and tests. It may look convoluted but it's structured that way to simulate my "real" project.
export function evaluate(resolve, reject) {
const data = this;
function getPromise(data) {
return !!data ? Promise.resolve(data) : Promise.reject(new Error("Error"));
}
getPromise(data)
.then(resolve)
.catch(reject);
}
ADDITIONAL_DATA is just a string to simulate the second value in the callback. It's also extracted to be consistent across all versions and tests.
------------------- Edit 2---------------------
Errors that come up depending on the solution
catch is not accessible
A lot of UnhandledPromiseRejectionWarning: warnings because errors/rejects are not getting propagated up correctly.
Errors/rejects are getting thrown too early and don't even reach the rejects checks in my test suites
You have problems (especially with unhandled rejections) because you are not implementing the then interface correctly. Remember that .catch(onRejected) is just an alias for .then(undefined, onRejected), and then with two parameters is the actual core method of every promise.
You were always ignoring the second argument, so no rejection ever got handled. You need to write
then(onFulfilled, onRejected) {
return super.then(res => onFulfilled(res, this.ADDITIONAL_DATA), onRejected);
// or `this.promise.then` instead of `super.then`
}
I don't understand very well why you do have a factory method, instead of using directly the constructor.
Do you mean something like this?
class MyExtendedPromise extends Promise {
constructor(executor, data) {
super(executor);
this.data = data;
}
then(callback, test) {
console.log('passed new parameter in then:', test);
console.log('additional data:', this.data);
return super.then(data => callback(data, test));
}
}
new MyExtendedPromise((resolve, reject) => {
setTimeout(() => resolve(true), 2000);
}, 'other additional data').then(data => console.log('my then', data), 'hello world');

Can't set properties on child class after async calling constructor of base class. NodeJS v8.2.1

Could you please help to understand what can be wrong while using promises in callback on parent class and then trying to set properties on child class?
I'm using nodejs v8.2.1
Here is example of base class:
class CGLBase extends CAuthClient
{
constructor(req, res)
{
return new Promise(resolve => {
super(req, res, (authClient) => {
this.setAuthClient(authClient);
resolve(this);
});
});
}
setAuthClient(authClient)
{
//setting authClient (this.auth will contain property)
}
}
And here example of child class:
class СSheet extends CGLBase
{
constructor(document, urlRequest, urlResponse)
{
super(urlRequest, urlResponse);
this.document = document;
this.someAnotherProp = "some property";
//etc..
}
someFunc()
{
//using this.document and this.auth
}
}
After that I'm creating instance of СSheet and trying to set properties:
var document = { ... }; //here I create object
(async () => {
var docSheet = await new СSheet (document, req, res);
docSheet.someFunc();
console.log(docSheet.auth); //return correct property
console.log(docSheet.document); //return undefined. why...
})();
So, I don't understand why property this.document wasn't set. I see only this.auth that was set in the async callback. All properties except this.auth are undefined.
I will be very grateful for advice or help.
Thank you in advance.
I don't understand why property this.document wasn't set. I see only this.auth that was set in the async callback.
Your CSheet constructor did set the document and someAnotherProp properties on the promise returned by super(). awaiting the new CSheet gives you the CAuthClient instance that the promise was resolved with, and which does not have the properties.
You could work around that by using
class СSheet extends CGLBase {
constructor(document, urlRequest, urlResponse) {
return super(urlRequest, urlResponse).then(instance => {
instance.document = document;
instance.someAnotherProp = "some property";
// …
return instance;
});
}
…
}
but you really absolutely never should do that. This of course is the fault of CAuthClient taking an asynchronous callback. Fix that class and all its children to do the asynchronous creation of authClient in a static helper method, and only pass plain values into the constructor.

Set global variable of class from inside a promise Angular 2

I am facing a weird issue in assigning response to a class's global variable from inside a observable. So my program logic is as follows:
Get latest playlists ID's from elastic search (i use elastic search from a type definition file). This returns me a PromiseLike to which i hook a then operator.
Inside the promise resolution, i make another http get call (i.e an observable)
In Observable subscription, i assign my global array with the response from the server.
Code is working correctly, I am getting responses as they should be but i cant assign the variable to the global one.
Here is my code:
import {Component, OnInit} from '#angular/core';
import {PlaylistService} from '../api/services'
#Component({
selector: 'app-playlists',
templateUrl: './playlists.component.html',
styleUrls: ['./playlists.component.css']
})
export class PlaylistsComponent implements OnInit {
public playlists: any[] = [];
constructor(private playlistService: PlaylistService) {
}
ngOnInit() {
let that = this;
this.playlistService.listIds().then((val) => { // <-- promise resolution
return this.playlistService.getByIds(val).toPromise(); // <-- http get call which i then convert to promise for simplicity
}).then((res) => { // <-- resolution of the http get call
console.log(this.playlists); <-- in this log, i get my desired results
// here is my problem, this assignment doesn't happens
this.playlists = res.data;
});
}
}
The listIds function is as follows:
listIds() {
return this.api.listing('playlist').then((body) => {
let hits = body.hits.hits;
return _.keys(_.groupBy(hits, '_id'));
});
}
and here is my api.listing function (elastic search client)
listing(type: string) {
let es = this.prepareES();
return es.search({
index: 'test',
_source: ["_id"],
type: type
});
}
The return type of es.search is
search(params: SearchParams): PromiseLike>;
Any ideas why i am not being able to assign value to global variable?
It looks like the promise returned by this.playlistservice.listIds() doesn't run inside Angulars zone. This is why Angular2 doesn't run change detection and doesn't recognize the change.
You can invoke change detection explicitly after the change:
constructor(private playlistService: PlaylistService, private cdRef:ChangeDetectorRef) {
...
ngOnInit() {
let that = this;
this.playlistService.listIds().then((val) => { // <-- promise resolution
return this.playlistService.getByIds(val).toPromise(); // <-- http get call which i then convert to promise for simplicity
}).then((res) => { // <-- resolution of the http get call
console.log(this.playlists); <-- in this log, i get my desired results
// here is my problem, this assignment doesn't happens
this.playlists = res.data;
this.cdRef.detectChanges();
});
}
Can you try passing
this.playlistService.listIds()
call inside your
return this.playlistService.getByIds(val)
replace val with first service call and see if your view gets updated. Just for testing purpose like
return this.playlistService.getByIds(this.playlistService.listIds())
.then((results)=>{/*rest of logic here*/});

Why does promise resolve with promise?

I have a promise in my code that consistently fails. When I return a promise to a Promise#then callback, rather than wait for the promise to resolve, it passes it as a parameter to the subsequent Promise#then call.
See code in this gist for more detailed code. Calling House#setEnergyData would cause the problem (see comments in gist).
The basic pattern that is causing problems in my code looks like this (although this literal code does work):
class Class1 {
step1(){
return new Promise((fnResolve, fnReject)=>{
// do something
fnResolve();
});
}
step2(){
return this.step1().then(()=>{
// do something else
});
}
}
class Class2 {
constructor(){
this.class1 = new Class1();
}
doSomething(){
this.class1.step2()
.then((res)=>{
console.log(res) // Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// do something that shouldn't happen until Class1#step2 has finished.
// error!
});
}
}
This is running in a browser and is compiled with Babel ES2015 preset and uses Babel polyfill. I'm not using any Promise npm module.
This has recurred in several places in my code. I'm not sure if it's an issue with the compilation process, babel polyfill, or I'm misunderstanding promises.
Try including return statement within .then() at step2 to return value from Promise at step2() call
class Class1 {
step1() {
return new Promise((fnResolve, fnReject) => {
// do something
fnResolve(123);
});
}
step2() {
return this.step1().then((data) => {
// do something else
// added `return` here
return data
});
}
}
class Class2 {
constructor() {
this.class1 = new Class1();
}
doSomething() {
this.class1.step2()
.then((res) => {
console.log(res) // 123
});
}
}
var c = new Class2();
c.doSomething();

Categories