-
-
Save KiaraGrouwstra/ac9c024e0996fda7c4d1290241e26277 to your computer and use it in GitHub Desktop.
// using ES6 Proxy to deal with methods of Promise'd objects. works for me in Edge though not Chrome somehow. | |
let handler = { | |
get: (target, prop) => function() { | |
if(target instanceof Promise) { | |
let args = arguments; | |
return target.then((o) => o[prop].apply(o, args)); | |
} else { | |
let value = target[prop]; | |
return typeof value == 'function' ? value.bind(target) : value; | |
} | |
} | |
}; | |
let obj = { greet: (name) => console.log('Hey ' + name) }; | |
let later = (v) => new Promise((resolve, reject) => setTimeout(() => resolve(v), 1000)) | |
let prom = later(obj); | |
let greeter = new Proxy(prom, handler); | |
greeter.greet('you'); |
Thanks! Let me add my PoC, that returns a Proxy of the original promise that projects the resolved value
(i.e the value passed to your .then(value => // do things with value)
const projectPromiseValueProxy = (aPromise, projectTo) =>
new Proxy(aPromise, {
get: (targetPromise, prop) => {
if (prop === 'then') {
const originalThen = targetPromise.then.bind(targetPromise);
const newThen = (function (thenFun) {
return originalThen(value => thenFun(projectTo(value)));
}).bind(targetPromise);
return newThen;
}
return targetPromise[prop];
}
});
const promise = new Promise(resolve => setTimeout(() => resolve({ body: 42 }), 1000));
const projectedPromise = projectPromiseValueProxy(promise, (value) => value.body * 10);
projectedPromise
.then(response => {
console.log(response); // prints 420
return { whatever: response };
})
.then(response => console.log(response)) // prints {whatever: 420}
.catch(console.error);
A slightly more involved version, if .finally
or .catch
are used before the first .then
, we need to proxy those promises too:
(edit: CAVEAT: this version actually throws UnhandledPromiseRejectionWarning
with rejected promises, if .catch
preceeds .then
- scroll below for the one that works!)
const projectPromiseValueProxy = (aPromise, projectTo) =>
new Proxy(aPromise, {
get: (targetPromise, prop) => {
// project value of the first .then
if (prop === 'then') {
const originalThen = targetPromise.then.bind(targetPromise);
const newThen = (function (userThenFunction) {
return originalThen(value => userThenFunction(projectTo(value)));
}).bind(targetPromise);
return newThen;
}
// when we have `promise.catch().finally().then()` (i.e finally/catch before first .then)
// we need to proxy until we find our first .then
if (prop === 'finally' || prop === 'catch') {
const originalFinallyOrCatch = targetPromise[prop].bind(targetPromise);
const newFinallyOrCatch = (function (userFinallyFunction) {
return projectPromiseValueProxy(originalFinallyOrCatch(userFinallyFunction), projectTo);
}).bind(targetPromise);
return newFinallyOrCatch;
}
return targetPromise[prop];
},
});
const promise = new Promise(resolve => setTimeout(() => resolve({ body: 42 }), 1000));
const projectedPromise = projectPromiseValueProxy(promise, (value) => value.body * 10);
projectedPromise
.catch(console.error)
.then(response => {
console.log(response); // prints 420
return { whatever: response };
})
.then(response => console.log(response)) // prints {whatever: 420} (i.e 2nd then its not proxied at all)
.finally(() => console.log('finito'))
so this is about retaining proxies across Promise chains right? that's kinda cool. :)
Thanks Kiarra! Yeah, its about projecting the value of the original promise to something else (i.e pass the value through projectTo
when its resolved).
This version works with rejected promises as well - its much less verbose also :-)
// the "main" function
const projectPromiseValueProxy = (aPromise, projectTo) =>
new Proxy(aPromise, {
get: (targetPromise, prop) => {
// project value of the first .then (only the first makes sense!)
if (prop === 'then')
return (userThenFunction) => targetPromise.then(value => userThenFunction(projectTo(value)))
// when we have `promise.catch().finally().then()` (i.e finally/catch before first .then)
// we need to proxy until we find our first .then
if (prop === 'finally' || prop === 'catch')
return (userFinallyOrCatchFunction) =>
projectPromiseValueProxy(targetPromise[prop](userFinallyOrCatchFunction), projectTo);
return targetPromise[prop];
},
});
// examples:
// resolving promise
const promise = new Promise(resolve => setTimeout(() => resolve({body: 42}), 1000));
const projectedPromise = projectPromiseValueProxy(promise, (value) => value?.body * 10);
projectedPromise
.catch(console.error)
.then(response => {
console.log(response); // prints 420
return {whatever: response};
})
.then(response => console.log(response)) // prints {whatever: 420} (i.e 2nd then its not proxied at all)
.finally(() => console.log('finito'))
// rejecting promise
// const rejectingPromise = Promise.reject('Shit !!!')
const rejectingPromise = new Promise((resolve, reject) => setTimeout(()=> reject('Shiiiiiiit!'), 2000))
const projectedRejectingPromise = projectPromiseValueProxy(rejectingPromise, (value) => value?.body * 10);
projectedRejectingPromise
.catch(err => console.error('rejectingPromise rejected:', err))
.then(response => {
console.log(response); // prints 420
return {whatever: response};
});
// rejecting promise, with try-catch block
(async () => {
try {
await projectedRejectingPromise // if you omit await here, you get `UnhandledPromiseRejectionWarning`
.then(response => {
console.log(response); // prints 420
return {whatever: response};
})
} catch (error) {
console.error('rejectingPromise try-catch :', error)
}
})();
See it in action https://jsbin.com/soxotad/edit?js,output
I'm curious now, did you have any particular use-case in mind with that? or just PoC like mine?
Sure, we wanted to convert our request-promise
requests from using resolveWithFullResponse
so we needed to extract body
from all results, as in the example above, without touching every single request...
In the end some tests were still failing with UnhandledPromiseRejectionWarning
, still not sure why.. so we had to backtrack from it... Interesting lesson though!
ohh cool I see, thanks! :)
I can say for sure, this can be helpful for "promisified" libraries. In our project I promisified google.script.run, to be able to work with Google Apps Script client code via promises and react-query. But mocking became like hell.
This kind of proxy wrapper allows me to mock the data during development without launching project in Sheets.
Thanks!
@bekharsky thanks - I know my code had caused us some issues back then (so we backtracked from it), please do let us know if you have encountered any issues and please post us any updated code if you fix them ;-)
Neat, I just wrote some very similar code (https://gist.github.com/Spensaur-K/5c1b8d32e5bfc8d5863778e2976f4799)
I find it odd that functions need to be bound to the target before being returned
(new Proxy(Promise.resolve(5), {})).then()
Generates a complaint for something I feel should be fine.