Why does promise resolve with promise? - javascript

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();

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.

Cancelable Promise loses cancel property when accessed in foreign class object

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.

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');

RxJS clarification needed: how to return a plain object instead of an Observable

I am writing an angular 2 validator function. I would like to return a plain object instead of an observable.
Here is my current implementation:
import {AbstractControl} from "#angular/common";
import {UserAccountService} from "../../useraccount/useraccount.service";
export function validateEmailAvailable(userAccountService: UserAccountService) {
return (control: AbstractControl)=> { //This returned lambda should itself return a plain object
return userAccountService.checkAvailability(control.value)
.map(res=> {
if (res.json() === true) {
return null;
}
else {
return {unavailable: true};
}
});
};
}
Can someone please explain how to properly use the RxJs operators in order to return either null or {unavailable: true} from the above but not an observable?
I would bet that this is not possible. This is the same as asking to ask an async function to return its value synchronously. Or returning a value in the present computed from values in the future. It is inherently contradictory. Promise 'operators' will always also return a promise, if you think about it.
You can use toPromise() to return a promise instead of an Observable.
For example:
export function validateEmailAvailable(userAccountService: UserAccountService) {
return (control: AbstractControl)=> { //This returned lambda should itself return a plain object
return userAccountService.checkAvailability(control.value)
.toPromise()
.then(res=> {
if (res.json() === true) {
return null;
}
else {
return {unavailable: true};
}
});
};
}
Instead of returning lambda why not directly return the observable containing the value?

Chained Promis in angularjs

I am new to angular and I am struggling to see how I should create a particular promise. I am also using typescript for coding.
I need to call a web service to authenticate this has to be done in 2 steps.
Step 1
Request an authentication key
Step 2
Process logon details with authentication key and return to service to retrieve the logged on user or error if incorrect
So I have created an angular service called AuthenticationService
like below (this of course doesn't work)
export class Authentication
{
$inject: string[] = ['$http'];
public Authenticate( userName: string, password: string ): ng.IHttpPromise<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>
{
$http.post( "/Account/AuthenticationKey", null )
.then<ApiModel.ApiResult<string>>( result =>
{
var strKey = userName + password; //TODO: Put correct code here!
var promiseToReturn = $http.post( "/Account/Authenticate", { key: strKey })
.then<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>( result =>
{
return result;
});
});
}
}
How do I go about returning a promise with the correct return type from the authentication method that returns the second result?
I can tell you that in JavaScript as i am not conversant with typescript. The idea is to create your own promise and resolve it whenever you want. Basically
var autenticate=function(user,password) {
var defer=$q.defer();
$http.post( "/Account/AuthenticationKey", null )
.then(function(data) {
//do something on data
$http.post( "/Account/Authenticate", { key: strKey })
.then(function(result) {
defer.resolve(result)
});
})
return defer.promise;
}
A then function should always either return another promise or a return value. The final then function should return a value which will get propagated to the top.
The relevant documentation can be found here :
https://github.com/kriskowal/q
NOTE : Angular's promise implementation is based on kriskowal's q.
This is the relevant section of the documentation :
If promiseMeSomething returns a promise that gets fulfilled later with
a return value, the first function (the fulfillment handler) will be
called with the value. However, if the promiseMeSomething function
gets rejected later by a thrown exception, the second function (the
rejection handler) will be called with the exception.
In your case, you should do something like
export class Authentication
{
$inject: string[] = ['$http'];
public Authenticate( userName: string, password: string ): ng.IHttpPromise<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>
{
return $http.post( "/Account/AuthenticationKey", null )
.then<ApiModel.ApiResult<string>>( result =>
{
var strKey = userName + password; //TODO: Put correct code here!
return $http.post( "/Account/Authenticate", { key: strKey })
.then<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>( result =>
{
return result;
});
});
}
}
Notice the two returns before the $http.posts that you are calling. All $http methods in Angular return a promise, which means you dont need to explicitly create another promise.

Categories