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');
Related
I am really new to Angular and Typescript.
I have this function right now
createTranscriptionJob(fileToUpload: File, language: string): Observable<void> {
this.getSasUrl(fileToUpload.name, language, ldapUserName)
.subscribe(response => {
const test = this.upload(response.sasUrl, fileToUpload);
console.log(test);
});
return of();
}
First problem currently is that the createTranscriptionJob function is returning basically nothing.
Second problem is that the createTranscriptionJob function is returning before the completion of the inner upload function call.
Question
I would need the createTranscriptionJob function to return the result of the upload function. How to achieve that?
Something like that, but it's complaining:
You'll need to chain your observables with an operator like switchMap.
Doing so, you will need the called to do the subscribe.
createTranscriptionJob(fileToUpload: File, language: string): Observable<void> {
return this.getSasUrl(fileToUpload.name, language, ldapUserName)
.pipe(
switchMap(() => this.upload(response.sasUrl, fileToUpload)
)
);
}
You can use alse the async/await pattern:
async createTranscriptionJob(fileToUpload: File, language: string) {
var response = await this.getSasUrl(fileToUpload.name, language, ldapUserName).toPromise();
// this line depends on the reponse type of upload method (toPromise() must be added if is an observable)
const test = await this.upload(response.sasUrl, fileToUpload);
console.log(test);
return test;
}
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.
I'm a new developper in Node.js (coming from Python), and I'm quite surprised to see that there are no decorators in Javascript. I would like to use it, however, because it greatly simplifies the code.
After some research, I found an ES6 specification in stage 2 for this (https://github.com/tc39/proposal-decorators), but it is apparently not supported in Node.js. I also found this in TypeScript, but their function is limited (only in classes).
So my question is: Is there a way to have decorators similar to those of Python with Node.js, and if not, what features can be used as a substitute ?
Thank you in advance for your answers !
According to Wikipedia decorators in python are just syntactic sugar for a function call, which takes the original class/function and stores the returned value under the variable containing the class. Thus the equivalent js would be:
const Decorated = decorator(class ToBeDecorated {
/*...*/
});
const decoratedFunction = decorateFunction(function decorated() {
/*...*/
});
// Some sample decorator implementations:
const decorator = Parent => class WithDuck extends Parent {
quack() { }
};
const decorateFunction = fn => (...args) => {
console.log(`${fn.name} called with`, ...args);
return fn(...args);
};
#jonas Wilms 's answer is correct. I wanted to post this here to expand on a specific implementation technique I used. My use case was with Koa, using async/await functions.
First, I created my decorator:
export function Cocoon( { param=[] }, callback )
{
//Return a function from the decorator
return async (ctx) => {
//Do a thing with Param
const params = { usr: param }
return await callback( ctx, params )
}
}
Using the decorator to annotate a function:
export const list = Cocoon({
param: [5]
},
async (ctx, { usr }) => {
//Function body
ctx.response.body = usr
}
)
Now I can export 'list' to Koa and it'll be called as expected.
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.
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();