async function external stack context - javascript

Sometimes code would like to know if a particular function (or children) are running or not. For instance, node.js has domains which works for async stuff as well (not sure if this includes async functions).
Some simple code to explain what I need would by like this:
inUpdate = true;
try {
doUpdate();
} finally {
inUpdate = false;
}
This could then be used something like:
function modifyThings() {
if (inUpdate) throw new Error("Can't modify while updating");
}
With the advent of async this code breaks if the doUpdate() function is asynchronous. This was of course already true using callback-style functions.
The doUpdate function could of course be patched to maintain the variable around every await, but even if you have control over the code, this is cumbersome and error prone and this breaks when trying to track async function calls inside doUpdate.
I tried monkey-patching Promise.prototype:
const origThen = Promise.prototype.then;
Promise.prototype.then = function(resolve, reject) {
const isInUpdate = inUpdate;
origThen.call(this, function myResolve(value) {
inUpdate = isInUpdate;
try {
return resolve(value);
} finally {
inUpdate = false;
}
}, reject);
}
Unfortunately this doesn't work. I'm not sure why, but the async continuation code ends up running outside of the resolve call stack (probably using a microtask).
Note that it's not enough to simply do:
function runUpdate(doUpdate) {
inUpdate = true;
doUpdate.then(() => inUpdate = false).catch(() => inUpdate = false);
}
The reason is:
runUpdate(longAsyncFunction);
console.log(inUpdate); // incorrectly returns true
Is there any way to track something from outside an async function so it's possible to tell if the function called, or any of its descendant calls are running?
I know that it's possible to simulate async functions with generators and yield, in which case we have control over the call stack (since we can call gen.next()) but this is a kludge which the advent of async functions just got around to solving, so I'm specifically looking for a solution that works with native (not Babel-generated) async functions.
Edit: To clarify the question: Is there's a way for outside code to know if a particular invocation of an async function is running or if it is suspended, assuming that this code is the caller of the async function. Whether it's running or not would be determined by a function that ultimately is called by the async function (somewhere in the stack).
Edit: To clarify some more: The intended functionality would be the same as domains in node.js, but also for the browser. Domains already work with Promises, so async functions probably work as well (not tested).

This code allows me to do what I want to a certain extent:
function installAsyncTrack() {
/* global Promise: true */
if (Promise.isAsyncTracker) throw new Error('Only one tracker can be installed');
const RootPromise = Promise.isAsyncTracker ? Promise.rootPromise : Promise;
let active = true;
const tracker = {
track(f, o, ...args) {
const prevObj = tracker.trackObj;
tracker.trackObj = o;
try {
return f.apply(this, args);
} finally {
tracker.trackObj = prevObj;
}
},
trackObj: undefined,
uninstall() {
active = false;
if (Promise === AsyncTrackPromise.prevPromise) return;
if (Promise !== AsyncTrackPromise) return;
Promise = AsyncTrackPromise.prevPromise;
}
};
AsyncTrackPromise.prototype = Object.create(Promise);
AsyncTrackPromise.rootPromise = RootPromise;
AsyncTrackPromise.prevPromise = Promise;
Promise = AsyncTrackPromise;
AsyncTrackPromise.resolve = value => {
return new AsyncTrackPromise(resolve => resolve(value));
};
AsyncTrackPromise.reject = val => {
return new AsyncTrackPromise((resolve, reject) => reject(value));
};
AsyncTrackPromise.all = iterable => {
const promises = Array.from(iterable);
if (!promises.length) return AsyncTrackPromise.resolve();
return new AsyncTrackPromise((resolve, reject) => {
let rejected = false;
let results = new Array(promises.length);
let done = 0;
const allPromises = promises.map(promise => {
if (promise && typeof promise.then === 'function') {
return promise;
}
return new AsyncTrackPromise.resolve(promise);
});
allPromises.forEach((promise, ix) => {
promise.then(value => {
if (rejected) return;
results[ix] = value;
done++;
if (done === results.length) {
resolve(results);
}
}, reason => {
if (rejected) return;
rejected = true;
reject(reason);
});
});
});
};
AsyncTrackPromise.race = iterable => {
const promises = Array.from(iterable);
if (!promises.length) return new AsyncTrackPromise(() => {});
return new AsyncTrackPromise((resolve, reject) => {
let resolved = false;
if (promises.some(promise => {
if (!promise || typeof promise.then !== 'function') {
resolve(promise);
return true;
}
})) return;
promises.forEach((promise, ix) => {
promise.then(value => {
if (resolved) return;
resolved = true;
resolve(value);
}, reason => {
if (resolved) return;
resolved = true;
reject(reason);
});
});
});
};
function AsyncTrackPromise(handler) {
const promise = new RootPromise(handler);
promise.trackObj = tracker.trackObj;
promise.origThen = promise.then;
promise.then = thenOverride;
promise.origCatch = promise.catch;
promise.catch = catchOverride;
if (promise.finally) {
promise.origFinally = promise.finally;
promise.finally = finallyOverride;
}
return promise;
}
AsyncTrackPromise.isAsyncTracker = true;
function thenOverride(resolve, reject) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origThen.apply(this, arguments);
return this.origThen.call(
this,
myResolver(trackObj, resolve),
reject && myResolver(trackObj, reject)
);
}
function catchOverride(reject) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origCatch.catch.apply(this, arguments);
return this.origCatch.call(
this,
myResolver(trackObj, reject)
);
}
function finallyOverride(callback) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origCatch.catch.apply(this, arguments);
return this.origCatch.call(
this,
myResolver(trackObj, reject)
);
}
return tracker;
function myResolver(trackObj, resolve) {
return function myResolve(val) {
if (trackObj === undefined) {
return resolve(val);
}
RootPromise.resolve().then(() => {
const prevObj = tracker.trackObj;
tracker.trackObj = trackObj;
RootPromise.resolve().then(() => {
tracker.trackObj = prevObj;
});
});
const prevObj = tracker.trackObj;
tracker.trackObj = trackObj;
try {
return resolve(val);
} finally {
tracker.trackObj = prevObj;
}
};
}
}
tracker = installAsyncTrack();
function track(func, value, ...args) {
return tracker.track(func, { value }, value, ...args);
}
function show(where, which) {
console.log('At call', where, 'from', which, 'the value is: ', tracker.trackObj && tracker.trackObj.value);
}
async function test(which, sub) {
show(1, which);
await delay(Math.random() * 100);
show(2, which);
if (sub === 'resolve') {
await Promise.resolve(test('sub'));
show(3, which);
}
if (sub === 'call') {
await test(which + ' sub');
show(3, which);
}
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
track(test, 'test1');
track(test, 'test2');
track(test, 'test3', 'resolve');
track(test, 'test4', 'call');
It replaces the native Promise with my own. This promise stores the current context (taskObj) on the promise.
When the .then callback or its ilk are called, it does the following:
It creates a new native promise that immediately resolves. This adds a new microtask to the queue (according to spec, so should be reliable).
It calls the original resolve or reject. At least in Chrome and Firefox, this generates another microtask onto the queue that will run next part of the async function. Not sure what the spec has to say about this yet. It also restores the context around the call so that if it's not await that uses it, no microtask gets added here.
The first microtask gets executed, which is my first (native) promise being resolved. This code restores the current context (taskObj). It also creates a new resolved promise that queues another microtask
The second microtask (if any) gets executed, running the JS in the async function to until it hits the next await or returns.
The microtask queued by the first microtask gets executed, which restores the context to what it was before the Promise resolved/rejected (should always be undefined, unless set outside a tracker.track(...) call).
If the intercepted promise is not native (e.g. bluebird), it still works because it restores the state during the resolve(...) (and ilk) call.
There's one situation which I can't seem to find a solution for:
tracker.track(async () => {
console.log(tracker.taskObj); // 'test'
await (async () => {})(); //This breaks because the promise generated is native
console.log(tracker.taskObj); // undefined
}, 'test')
A workaround is to wrap the promise in Promise.resolve():
tracker.track(async () => {
console.log(tracker.taskObj); // 'test'
await Promise.resolve((async () => {})());
console.log(tracker.taskObj); // undefined
}, 'test')
Obviously, a lot of testing for all the different environments is needed and the fact that a workaround for sub-calls is needed is painful. Also, all Promises used need to either be wrapped in Promise.resolve() or use the global Promise.

[is it] possible to tell if the function called, or any of its descendant calls are running?
Yes. The answer is always no. Cause there is only one piece of code running at a time. Javascript is single threaded per definition.

Don't make it any more complicated than it needs to be. If doUpdate returns a promise (like when it is an async function), just wait for that:
inUpdate = true;
try {
await doUpdate();
//^^^^^
} finally {
inUpdate = false;
}
You can also use the finally Promise method:
var inUpdate = true;
doUpdate().finally(() => {
inUpdate = false;
});
That'll do just like like your synchronous code, having inUpdate == true while the function call or any of its descendants are running. Of course that only works if the asynchronous function doesn't settle the promise before it is finished doing its thing. And if you feel like the inUpdate flag should only be set during some specific parts of the doUpdate function, then yes the function will need to maintain the flag itself - just like it is the case with synchronous code.

Related

Trying to use `debounce` to throttle API requests, but getting "js Uncaught (in promise) TypeError: [function] is undefined" [duplicate]

I'm trying to implement a debounce function that works with a promise in javascript. That way, each caller can consume the result of the "debounced" function using a Promise. Here is the best I have been able to come up with so far:
function debounce(inner, ms = 0) {
let timer = null;
let promise = null;
const events = new EventEmitter(); // do I really need this?
return function (...args) {
if (timer == null) {
promise = new Promise(resolve => {
events.once('done', resolve);
});
} else {
clearTimeout(timer);
}
timer = setTimeout(() => {
events.emit('done', inner(...args));
timer = null;
}, ms);
return promise;
};
}
Ideally, I would like to implement this utility function without introducing a dependency on EventEmitter (or implementing my own basic version of EventEmitter), but I can't think of a way to do it. Any thoughts?
I found a better way to implement this with promises:
function debounce(inner, ms = 0) {
let timer = null;
let resolves = [];
return function (...args) {
// Run the function after a certain amount of time
clearTimeout(timer);
timer = setTimeout(() => {
// Get the result of the inner function, then apply it to the resolve function of
// each promise that has been created since the last time the inner function was run
let result = inner(...args);
resolves.forEach(r => r(result));
resolves = [];
}, ms);
return new Promise(r => resolves.push(r));
};
}
I still welcome suggestions, but the new implementation answers my original question about how to implement this function without a dependency on EventEmitter (or something like it).
In Chris's solution all calls will be resolved with delay between them, which is good, but sometimes we need resolve only last call.
In my implementation, only last call in interval will be resolved.
function debounce(f, interval) {
let timer = null;
return (...args) => {
clearTimeout(timer);
return new Promise((resolve) => {
timer = setTimeout(
() => resolve(f(...args)),
interval,
);
});
};
}
And the following typescript(>=4.5) implementation supports aborted features:
Support aborting promise via reject(). If we don't abort it, it cannot execute finally function.
Support custom reject abortValue.
If we catch error, we may need to determine if the error type is Aborted
/**
*
* #param f callback
* #param wait milliseconds
* #param abortValue if has abortValue, promise will reject it if
* #returns Promise
*/
export function debouncePromise<T extends (...args: any[]) => any>(
fn: T,
wait: number,
abortValue: any = undefined,
) {
let cancel = () => { };
// type Awaited<T> = T extends PromiseLike<infer U> ? U : T
type ReturnT = Awaited<ReturnType<T>>;
const wrapFunc = (...args: Parameters<T>): Promise<ReturnT> => {
cancel();
return new Promise((resolve, reject) => {
const timer = setTimeout(() => resolve(fn(...args)), wait);
cancel = () => {
clearTimeout(timer);
if (abortValue!==undefined) {
reject(abortValue);
}
};
});
};
return wrapFunc;
}
/**
// deno run src/utils/perf.ts
function add(a: number) {
return Promise.resolve(a + 1);
}
const wrapFn= debouncePromise(add, 500, 'Aborted');
wrapFn(2).then(console.log).catch(console.log).finally(()=>console.log('final-clean')); // Aborted + final-clean
wrapFn(3).then(console.log).catch(console.log).finally(()=>console.log('final-clean')); // 4 + final_clean
Note:
I had done some memory benchmarks, huge number of pending promises won't cause memory leak. It seems that V8 engine GC will clean unused promises.
I landed here because I wanted to get the return value of the promise, but debounce in underscore.js was returning undefined instead. I ended up using lodash version with leading=true. It works for my case because I don't care if the execution is leading or trailing.
https://lodash.com/docs/4.17.4#debounce
_.debounce(somethingThatReturnsAPromise, 300, {
leading: true,
trailing: false
})
resolve one promise, cancel the others
Many implementations I've seen over-complicate the problem or have other hygiene issues. In this post we will write our own debounce. This implementation will -
have at most one promise pending at any given time (per debounced task)
stop memory leaks by properly cancelling pending promises
resolve only the latest promise
demonstrate proper behaviour with live code demos
We write debounce with its two parameters, the task to debounce, and the amount of milliseconds to delay, ms. We introduce a single local binding for its local state, t -
function debounce (task, ms) {
let t = { promise: null, cancel: _ => void 0 }
return async (...args) => {
try {
t.cancel()
t = deferred()
await t.promise
await task(...args)
}
catch (_) { /* prevent memory leak */ }
}
}
We depend on a reusable deferred function, which creates a new promise that resolves in ms milliseconds. It introduces two local bindings, the promise itself, an the ability to cancel it -
function deferred (ms) {
let cancel, promise = new Promise((resolve, reject) => {
cancel = reject
setTimeout(resolve, ms)
})
return { promise, cancel }
}
click counter example
In this first example, we have a button that counts the user's clicks. The event listener is attached using debounce, so the counter is only incremented after a specified duration -
// debounce, deferred
function debounce (task, ms) { let t = { promise: null, cancel: _ => void 0 }; return async (...args) => { try { t.cancel(); t = deferred(ms); await t.promise; await task(...args); } catch (_) { console.log("cleaning up cancelled promise") } } }
function deferred (ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } }
// dom references
const myform = document.forms.myform
const mycounter = myform.mycounter
// event handler
function clickCounter (event) {
mycounter.value = Number(mycounter.value) + 1
}
// debounced listener
myform.myclicker.addEventListener("click", debounce(clickCounter, 1000))
<form id="myform">
<input name="myclicker" type="button" value="click" />
<output name="mycounter">0</output>
</form>
live query example, "autocomplete"
In this second example, we have a form with a text input. Our search query is attached using debounce -
// debounce, deferred
function debounce (task, ms) { let t = { promise: null, cancel: _ => void 0 }; return async (...args) => { try { t.cancel(); t = deferred(ms); await t.promise; await task(...args); } catch (_) { console.log("cleaning up cancelled promise") } } }
function deferred (ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } }
// dom references
const myform = document.forms.myform
const myresult = myform.myresult
// event handler
function search (event) {
myresult.value = `Searching for: ${event.target.value}`
}
// debounced listener
myform.myquery.addEventListener("keypress", debounce(search, 1000))
<form id="myform">
<input name="myquery" placeholder="Enter a query..." />
<output name="myresult"></output>
</form>
Here's my version in typescript (mostly based on Chris one), if someone need it 😉
function promiseDebounce (exec: (...args: any[]) => Promise<any>, interval: number): () => ReturnType<typeof exec> {
let handle: number | undefined;
let resolves: Array<(value?: unknown) => void> = [];
return async (...args: unknown[]) => {
clearTimeout(handle);
handle = setTimeout(
() => {
const result = exec(...args);
resolves.forEach(resolve => resolve(result));
resolves = [];
},
interval
);
return new Promise(resolve => resolves.push(resolve));
};
}
No clue what you are trying to accomplish as it vastly depends on what your needs are. Below is something somewhat generic though. Without a solid grasp of what is going on in the code below, you really might not want to use it though.
// Debounce state constructor
function debounce(f) {
this._f = f;
return this.run.bind(this)
}
// Debounce execution function
debounce.prototype.run = function() {
console.log('before check');
if (this._promise)
return this._promise;
console.log('after check');
return this._promise = this._f(arguments).then(function(r) {
console.log('clearing');
delete this._promise; // remove deletion to prevent new execution (or remove after timeout?)
return r;
}.bind(this)).catch(function(r) {
console.log('clearing after rejection');
delete this._promise; // Remove deletion here for as needed as noted above
return Promise.reject(r); // rethrow rejection
})
}
// Some function which returns a promise needing debouncing
function test(str) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('test' + str);
resolve();
}, 1000);
});
}
a = new debounce(test); // Create debounced version of function
console.log("p1: ", p1 = a(1));
console.log("p2: ", p2 = a(2));
console.log("p1 = p2", p1 === p2);
setTimeout(function() {
console.log("p3: ", p3 = a(3));
console.log("p1 = p3 ", p1 === p3, " - p2 = p3 ", p2 === p3);
}, 2100)
View the console when running the code above. I put a few messages to show a bit about what is going on. First some function which returns a promise is passed as an argument to new debounce(). This creates a debounced version of the function.
When you run the debounced function as the code above does (a(1), a(2), and a(3)) you will notice during processing it returns the same promise instead of starting a new one. Once the promise is complete it removes the old promise. In code above I wait for the timeout manually with setTimeout before running a(3).
You can clear the promise in other ways as well, like adding a reset or clear function on debounce.prototype to clear the promise at a different time. You could also set it to timeout. The tests in the console log should show p1 and p2 get the same promise (reference comparison "===" is true) and that p3 is different.
Here is what I came up with to solve this issue. All calls to the debounced function batched to the same invocation all return the same Promise that resolves to the result of the future invocation.
function makeFuture() {
let resolve;
let reject;
let promise = new Promise((d, e) => {
resolve = d;
reject = e;
});
return [promise, resolve, reject];
}
function debounceAsync(asyncFunction, delayMs) {
let timeout;
let [promise, resolve, reject] = makeFuture();
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(async () => {
const [prevResolve, prevReject] = [resolve, reject];
[promise, resolve, reject] = makeFuture();
try {
prevResolve(await asyncFunction.apply(this, args));
} catch (error) {
prevReject(error);
}
}, delayMs);
return promise;
}
}
const start = Date.now();
const dog = {
sound: 'woof',
bark() {
const delay = Date.now() - start;
console.log(`dog says ${this.sound} after ${delay} ms`);
return delay;
},
};
dog.bark = debounceAsync(dog.bark, 50);
Promise.all([dog.bark(), dog.bark()]).then(([delay1, delay2]) => {
console.log(`Delay1: ${delay1}, Delay2: ${delay2}`);
});
Both Chris and Николай Гордеев have good solutions. The first will resolve all of them. The problem is that they all be resolved, but usually you wouldn't want all of them to run.
The second solution solved that but created a new problem - now you will have multiple awaits. If it's a function that is called a lot (like search typing) you might have a memory issue. I fixed it by creating the following asyncDebounce that will resolve the last one and reject (and the awaiting call will get an exception that they can just catch).
const debounceWithRejection = (
inner,
ms = 0,
reject = false,
rejectionBuilder
) => {
let timer = null;
let resolves = [];
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
const resolvesLocal = resolves;
resolves = [];
if (reject) {
const resolve = resolvesLocal.pop();
resolve.res(inner(...args));
resolvesLocal.forEach((r, i) => {
!!rejectionBuilder ? r.rej(rejectionBuilder(r.args)) : r.rej(r.args);
});
} else {
resolvesLocal.forEach((r) => r.res(inner(...args)));
}
resolves = [];
}, ms);
return new Promise((res, rej) =>
resolves.push({ res, rej, args: [...args] })
);
};
};
The rejection logic is optional, and so is the rejectionBuilder. It's an option to reject with specific builder so you will know to catch it.
You can see runing example.
This may not what you want, but can provide you some clue:
/**
* Call a function asynchronously, as soon as possible. Makes
* use of HTML Promise to schedule the callback if available,
* otherwise falling back to `setTimeout` (mainly for IE<11).
* #type {(callback: function) => void}
*/
export const defer = typeof Promise=='function' ?
Promise.resolve().then.bind(Promise.resolve()) : setTimeout;

How to write polyfill of promise which works for Promise.resolve()?

I'm trying to write a promise polyfill to get a better understanding of promise.
I've searched the internet and found a code which I'm able to understand to some extent.
function CustomPromise(executor) {
var state=PENDING;
var value = null;
var handlers=[];
var catchers = [];
function resolve(result) {
if(state!==PENDING) return;
state=FULFILLED;
value = result;
handlers.forEach((h)=>h(value)); //this line
}
function reject(error) {
if(state!==PENDING)return;
state=REJECTED;
value=error;
catchers.forEach(c=>c(value)); // this line
}
this.then = function(successCallback) {
if(state === FULFILLED) {
successCallback(value);
}else {
handlers.push(successCallback);
}
return this;
}
this.catch = function(failureCallback) {
if(state===REJECTED){
failureCallback(value)
} else {
catchers.push(value);
}
}
executor(resolve,reject);
}
Even in this I'm unable to understand the use of handlers and catchers. It was said that they are for situation when promise is not fulfilled or rejected. Explaining these two lines will also help.
Now, the actual issue with above implementation is it doesn't work for when used like let p1 = Promise.resolve("Hello World");. I have tried converting it to class based but I'm unable to do that.
My attempt:
class CustomPromise {
constructor(callback){
this.state = PENDING;
this.executor = callback;
this.value = null;
this.handlers = [];
this.catchers = [];
this.then = function(successCallback) {
if(this.state === FULFILLED) {
successCallback(this.value);
}else {
this.handlers.push(successCallback);
}
return this;
};
this.catch = function(failureCallback) {
if(this.state===REJECTED){
failureCallback(this.value)
} else {
this.catchers.push(this.value);
}
};
}
static resolve(result) {
if(this.state!==PENDING) return;
this.state=FULFILLED;
this.value = result;
this.handlers.forEach((h)=>h(this.value));
// return new CustomPromise( function ( fulfil ) {
// fulfil( value );
// });
}
static reject(error) {
if(this.state!==PENDING)return;
this.state=REJECTED;
this.value=error;
this.catchers.forEach(c=>c(this.value));
}
// executor(resolve,reject);
}
Can someone correct the functional approach so that it works for CustomPromise.resolve() scenario or correction in my class based approach will also be appreciated.
EDIT: Tried CustomPromise.prototype.resolve = function(error) {...}
still getting same error CustomPromise.resolve is not a function
EDIT2 : In class based approach I'm unable to implement executor callback. I just want either one of the approach to work for case like Promise.resolve()
To extend the first (working) version of CustomPromise, add the code you seem to have been trying with in commented-out code in the (non-working) class-version you had. But it had two little problems. Still, the idea to use new CustomPromise is the right one:
CustomPromise.resolve = value => new CustomPromise(fulfil => fulfil(value));
CustomPromise.reject = error => new CustomPromise((_, reject) => reject(error));
So if you add that below your CustomPromise function definition, it'll work:
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function CustomPromise(executor) {
var state=PENDING;
var value = null;
var handlers=[];
var catchers = [];
function resolve(result) {
if(state!==PENDING) return;
state=FULFILLED;
value = result;
handlers.forEach((h)=>h(value));
}
function reject(error) {
if(state!==PENDING)return;
state=REJECTED;
value=error;
catchers.forEach(c=>c(value));
}
this.then = function(successCallback) {
if(state === FULFILLED) {
successCallback(value);
}else {
handlers.push(successCallback);
}
return this;
}
this.catch = function(failureCallback) {
if(state===REJECTED){
failureCallback(value)
} else {
catchers.push(value);
}
}
executor(resolve,reject);
}
// Added:
CustomPromise.resolve = value => new CustomPromise(fulfil => fulfil(value));
CustomPromise.reject = error => new CustomPromise((_, reject) => reject(error));
// Demo
let p = CustomPromise.resolve(42);
let q = CustomPromise.reject("custom error");
p.then(value => console.log("p", value));
q.catch(error => console.log("q", error));
Disclaimer: this polyfill is not compliant with the Promises/A+ specification let be it would be compliant with the ECMAScript specification for Promise.
Some of the problems it has:
It allows the then callback to be executed synchronously. The then callback should never be executed synchronously, but always be called via a queued job (i.e. asynchronously).
The catch method wrongly returns undefined. It should return a promise.
The then method wrongly returns the same promise as it is called on. It should return a different promise, that resolves with the value that will be returned by the callback.
The then method ignores a second argument. It should take a second function which should serve as a rejection callback (like catch).
When the promise is resolved with a thenable, the thenable is wrongly used as fulfillment value. This is not how it is supposed to work. A very important aspect of promises, is that promises chain, i.e. when a promise resolves to another promise (or thenable), it gets "locked in" to that second promise, so that it will follow the way that second promise resolves.
There are many correct implementations to be found. I posted my own Promises/A+ compliant implementation in this answer. It does not have the resolve and reject static methods, but it would require the same additional code as given above.

Node.js I keep getting Promise { <pending> } when I make an API call then try to save results to variable and then log to console [duplicate]

I read that async functions marked by the async keyword implicitly return a promise:
async function getVal(){
return await doSomethingAync();
}
var ret = getVal();
console.log(ret);
but that is not coherent...assuming doSomethingAsync() returns a promise, and the await keyword will return the value from the promise, not the promise itsef, then my getVal function should return that value, not an implicit promise.
So what exactly is the case? Do functions marked by the async keyword implicitly return promises or do we control what they return?
Perhaps if we don't explicitly return something, then they implicitly return a promise...?
To be more clear, there is a difference between the above and
function doSomethingAync(charlie) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(charlie || 'yikes');
}, 100);
})
}
async function getVal(){
var val = await doSomethingAync(); // val is not a promise
console.log(val); // logs 'yikes' or whatever
return val; // but this returns a promise
}
var ret = getVal();
console.log(ret); //logs a promise
In my synopsis the behavior is indeed inconsistent with traditional return statements. It appears that when you explicitly return a non-promise value from an async function, it will force wrap it in a promise.
I don't have a big problem with it, but it does defy normal JS.
The return value will always be a promise. If you don't explicitly return a promise, the value you return will automatically be wrapped in a promise.
async function increment(num) {
return num + 1;
}
// Even though you returned a number, the value is
// automatically wrapped in a promise, so we call
// `then` on it to access the returned value.
//
// Logs: 4
increment(3).then(num => console.log(num));
Same thing even if there's no return! (Promise { undefined } is returned)
async function increment(num) {}
Same thing even if there's an await.
function defer(callback) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(callback());
}, 1000);
});
}
async function incrementTwice(num) {
const numPlus1 = await defer(() => num + 1);
return numPlus1 + 1;
}
// Logs: 5
incrementTwice(3).then(num => console.log(num));
Promises auto-unwrap, so if you do return a promise for a value from within an async function, you will receive a promise for the value (not a promise for a promise for the value).
function defer(callback) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(callback());
}, 1000);
});
}
async function increment(num) {
// It doesn't matter whether you put an `await` here.
return defer(() => num + 1);
}
// Logs: 4
increment(3).then(num => console.log(num));
In my synopsis the behavior is indeed inconsistent with traditional
return statements. It appears that when you explicitly return a
non-promise value from an async function, it will force wrap it in a
promise. I don't have a big problem with it, but it does defy normal
JS.
ES6 has functions which don't return exactly the same value as the return. These functions are called generators.
function* foo() {
return 'test';
}
// Logs an object.
console.log(foo());
// Logs 'test'.
console.log(foo().next().value);
Yes, an async function will always return a promise.
According to the tc39 spec, an async function desugars to a generator which yields Promises.
Specifically:
async function <name>?<argumentlist><body>
Desugars to:
function <name>?<argumentlist>{ return spawn(function*() <body>, this); }
Where spawn "is a call to the following algorithm":
function spawn(genF, self) {
return new Promise(function(resolve, reject) {
var gen = genF.call(self);
function step(nextF) {
var next;
try {
next = nextF();
} catch(e) {
// finished with failure, reject the promise
reject(e);
return;
}
if(next.done) {
// finished with success, resolve the promise
resolve(next.value);
return;
}
// not finished, chain off the yielded promise and `step` again
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
Your question is: If I create an async function should it return a promise or not? Answer: just do whatever you want and Javascript will fix it for you.
Suppose doSomethingAsync is a function that returns a promise. Then
async function getVal(){
return await doSomethingAsync();
}
is exactly the same as
async function getVal(){
return doSomethingAsync();
}
You probably are thinking "WTF, how can these be the same?" and you are right. The async will magically wrap a value with a Promise if necessary.
Even stranger, the doSomethingAsync can be written to sometimes return a promise and sometimes NOT return a promise. Still both functions are exactly the same, because the await is also magic. It will unwrap a Promise if necessary but it will have no effect on things that are not Promises.
Just add await before your function when you call it :
var ret = await getVal();
console.log(ret);
async doesn't return the promise, the await keyword awaits the resolution of the promise. async is an enhanced generator function and await works a bit like yield
I think the syntax (I am not 100% sure) is
async function* getVal() {...}
ES2016 generator functions work a bit like this. I have made a database handler based in top of tedious which you program like this
db.exec(function*(connection) {
if (params.passwd1 === '') {
let sql = 'UPDATE People SET UserName = #username WHERE ClinicianID = #clinicianid';
let request = connection.request(sql);
request.addParameter('username',db.TYPES.VarChar,params.username);
request.addParameter('clinicianid',db.TYPES.Int,uid);
yield connection.execSql();
} else {
if (!/^\S{4,}$/.test(params.passwd1)) {
response.end(JSON.stringify(
{status: false, passwd1: false,passwd2: true}
));
return;
}
let request = connection.request('SetPassword');
request.addParameter('userID',db.TYPES.Int,uid);
request.addParameter('username',db.TYPES.NVarChar,params.username);
request.addParameter('password',db.TYPES.VarChar,params.passwd1);
yield connection.callProcedure();
}
response.end(JSON.stringify({status: true}));
}).catch(err => {
logger('database',err.message);
response.end(JSON.stringify({status: false,passwd1: false,passwd2: false}));
});
Notice how I just program it like normal synchronous particularly at
yield connection.execSql and at yield connection.callProcedure
The db.exec function is a fairly typical Promise based generator
exec(generator) {
var self = this;
var it;
return new Promise((accept,reject) => {
var myConnection;
var onResult = lastPromiseResult => {
var obj = it.next(lastPromiseResult);
if (!obj.done) {
obj.value.then(onResult,reject);
} else {
if (myConnection) {
myConnection.release();
}
accept(obj.value);
}
};
self._connection().then(connection => {
myConnection = connection;
it = generator(connection); //This passes it into the generator
onResult(); //starts the generator
}).catch(error => {
reject(error);
});
});
}

Resolve promise on array push - javascript

I'm attempting to define a function that returns a promise. The promise should resolve when a given array is set (push()).
To do this I'm attempting to use a Proxy object (influenced by this):
let a = []
;(async function(){
const observe = array => new Promise(resolve =>
new Proxy(array, {
set(array, key, val) {
array[key] = val;
resolve();
}
}));
while(true){
await observe(a);
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:",`${a.pop()}`);
}
})(a);
;(async function(){
await new Promise(resolve => timerID = setTimeout(resolve, 2000))
a.push('ʕ·͡ᴥ·ʔ');
a.push('¯\(°_o)/¯ ')
})(a)
I can't see why this doesn't work. Does anyone have any idea?
More generally, what is a good way to have a promise resolve on push to an array?
The problems with your attempt:
you invoke .push on the original array, not the proxied one. Where you create the proxy, it is returned to no-one: any reference to it is lost (and will be garbage collected).
The code following after the line with await will execute asynchronously, so after all of your push calls have already executed. That means that console.log will execute when the array already has two elements. Promises are thus not the right tool for what you want, as the resolution of a promise can only be acted upon when all other synchronous code has run to completion. To get notifications during the execution synchronously, you need a synchronous solution, while promises are based on asynchronous execution.
Just to complete the answer, I provide here a simple synchronous callback solution:
function observed(array, cb) {
return new Proxy(array, {
set(array, key, val) {
array[key] = val;
if (!isNaN(key)) cb(); // now it is synchronous
return true;
}
});
}
let a = observed([], () =>
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:", `${a.pop()}`)
);
a.push('ʕ·͡ᴥ·ʔ');
a.push('¯\(°_o)/¯ ');
As noted before: promises are not the right tool when you need synchronous code execution.
When each push is executed asynchronously
You can use promises, if you are sure that each push happens in a separate task, where the promise job queue is processed in between every pair of push calls.
For instance, if you make each push call as part of an input event handler, or as the callback for a setTimeout timer, then it is possible:
function observed(array) {
let resolve = () => null; // dummy
let proxy = new Proxy(array, {
set(array, key, val) {
array[key] = val;
if (!isNaN(key)) resolve();
return true;
}
});
proxy.observe = () => new Promise(r => resolve = r);
return proxy;
}
let a = observed([]);
(async () => {
while (true) {
await a.observe();
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:",`${a.pop()}`);
}
})();
setTimeout(() => a.push('ʕ·͡ᴥ·ʔ'), 100);
setTimeout(() => a.push('¯\(°_o)/¯ '), 100);

Use Promise to wait until polled condition is satisfied

I need to create a JavaScript Promise that will not resolve until a specific condition is true. Let's say I have a 3rd party library, and I need to wait until a certain data condition exists within that library.
The scenario I am interested in is one where there is no way to know when this condition is satisfied other than by simply polling.
I can create a promise that waits on it - and this code works, but is there a better or more concise approach to this problem?
function ensureFooIsSet() {
return new Promise(function (resolve, reject) {
waitForFoo(resolve);
});
}
function waitForFoo(resolve) {
if (!lib.foo) {
setTimeout(waitForFoo.bind(this, resolve), 30);
} else {
resolve();
}
}
Usage:
ensureFooIsSet().then(function(){
...
});
I would normally implement a max poll time, but didn't want that to cloud the issue here.
A small variation would be to use a named IIFE so that your code is a little more concise and avoids polluting the external scope:
function ensureFooIsSet() {
return new Promise(function (resolve, reject) {
(function waitForFoo(){
if (lib.foo) return resolve();
setTimeout(waitForFoo, 30);
})();
});
}
Here's a waitFor function that I use quite a bit. You pass it a function, and it checks and waits until the function returns a truthy value, or until it times out.
This is a simple version which illustrates what the function does, but you might want to use the full version, added further in the answer
let sleep = ms => new Promise(r => setTimeout(r, ms));
let waitFor = async function waitFor(f){
while(!f()) await sleep(1000);
return f();
};
Example usages:
wait for an element to exist, then assign it to a variable
let bed = await waitFor(() => document.getElementById('bedId'))
if(!bed) doSomeErrorHandling();
wait for a variable to be truthy
await waitFor(() => el.loaded)
wait for some test to be true
await waitFor(() => video.currentTime > 21)
add a specific timeout to stop waiting
await waitFor(() => video.currentTime > 21, 60*1000)
pass it some other test function
if(await waitFor(someTest)) console.log('test passed')
else console.log("test didn't pass after 20 seconds")
Full Version:
This version takes cares of more cases than the simple version, null, undefined, empty array, etc., has a timeout, a frequency can be passed as an argument, and logs to the console what it is doing with some nice colors
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms));}
/**
* Waits for the test function to return a truthy value
* example usage:
* wait for an element to exist, then save it to a variable
* let el = await waitFor(() => document.querySelector('#el_id')))
* timeout_ms and frequency are optional parameters
*/
async function waitFor(test, timeout_ms = 20 * 1000, frequency = 200) {
if (typeof (test) != "function") throw new Error("test should be a function in waitFor(test, [timeout_ms], [frequency])")
if (typeof (timeout_ms) != "number") throw new Error("timeout argument should be a number in waitFor(test, [timeout_ms], [frequency])");
if (typeof (frequency) != "number") throw new Error("frequency argument should be a number in waitFor(test, [timeout_ms], [frequency])");
let logPassed = () => console.log('Passed: ', test);
let logTimedout = () => console.log('%c' + 'Timeout : ' + test, 'color:#cc2900');
let last = Date.now();
let logWaiting = () => {
if(Date.now() - last > 1000) {
last = Date.now();
console.log('%c' + 'waiting for: ' + test, 'color:#809fff');
}
}
let endTime = Date.now() + timeout_ms;
let isNotTruthy = (val) => val === undefined || val === false || val === null || val.length === 0; // for non arrays, length is undefined, so != 0
let result = test();
while (isNotTruthy(result)) {
if (Date.now() > endTime) {
logTimedout();
return false;
}
logWaiting();
await sleep(frequency);
result = test();
}
logPassed();
return result;
}
Is there a more concise approach to this problem?
Well, with that waitForFoo function you don't need an anonymous function in your constructor at all:
function ensureFooIsSet() {
return new Promise(waitForFoo);
}
To avoid polluting the scope, I would recommend to either wrap both in an IIFE or to move the waitForFoo function inside the ensureFooIsSet scope:
function ensureFooIsSet(timeout) {
var start = Date.now();
return new Promise(waitForFoo);
function waitForFoo(resolve, reject) {
if (window.lib && window.lib.foo)
resolve(window.lib.foo);
else if (timeout && (Date.now() - start) >= timeout)
reject(new Error("timeout"));
else
setTimeout(waitForFoo.bind(this, resolve, reject), 30);
}
}
Alternatively, to avoid the binding that is needed to pass around resolve and reject you could move it inside the Promise constructor callback like #DenysSéguret suggested.
Is there a better approach?
Like #BenjaminGruenbaum commented, you could watch the .foo property to be assigned, e.g. using a setter:
function waitFor(obj, prop, timeout, expected) {
if (!obj) return Promise.reject(new TypeError("waitFor expects an object"));
if (!expected) expected = Boolean;
var value = obj[prop];
if (expected(value)) return Promise.resolve(value);
return new Promise(function(resolve, reject) {
if (timeout)
timeout = setTimeout(function() {
Object.defineProperty(obj, prop, {value: value, writable:true});
reject(new Error("waitFor timed out"));
}, timeout);
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
get: function() { return value; },
set: function(v) {
if (expected(v)) {
if (timeout) cancelTimeout(timeout);
Object.defineProperty(obj, prop, {value: v, writable:true});
resolve(v);
} else {
value = v;
}
}
});
});
// could be shortened a bit using "native" .finally and .timeout Promise methods
}
You can use it like waitFor(lib, "foo", 5000).
Here's a utility function using async/await and default ES6 promises. The promiseFunction is an async function (or just a function that returns a promise) that returns a truthy value if the requirement is fulfilled (example below).
const promisePoll = (promiseFunction, { pollIntervalMs = 2000 } = {}) => {
const startPoll = async resolve => {
const startTime = new Date()
const result = await promiseFunction()
if (result) return resolve()
const timeUntilNext = Math.max(pollIntervalMs - (new Date() - startTime), 0)
setTimeout(() => startPoll(resolve), timeUntilNext)
}
return new Promise(startPoll)
}
Example usage:
// async function which returns truthy if done
const checkIfOrderDoneAsync = async (orderID) => {
const order = await axios.get(`/order/${orderID}`)
return order.isDone
}
// can also use a sync function if you return a resolved promise
const checkIfOrderDoneSync = order => {
return Promise.resolve(order.isDone)
}
const doStuff = () => {
await promisePoll(() => checkIfOrderDone(orderID))
// will wait until the poll result is truthy before
// continuing to execute code
somethingElse()
}
function getReportURL(reportID) {
return () => viewReportsStatus(reportID)
.then(res => JSON.parse(res.body).d.url);
}
function pollForUrl(pollFnThatReturnsAPromise, target) {
if (target) return P.resolve(target);
return pollFnThatReturnsAPromise().then(someOrNone => pollForUrl(pollFnThatReturnsAPromise, someOrNone));
}
pollForUrl(getReportURL(id), null);

Categories

Resources