Javascript Promise Sequence - javascript

I want to process a number of promises in Sequence. I have a working piece of code below but I'm wondering if I have over complicated the chaining of promises. I seem to be creating a great deal of new closures and I'm scratching my head wondering if I'm missing something.
Is there a better way to write this function:
'use strict';
addElement("first")
.then(x => {return addElement("second")})
.then(x => { return addElement("third")})
.then(x => { return addElement("fourth")})
function addElement(elementText){
var myPromise = new Promise(function(resolve,reject){
setTimeout(function(){
var element=document.createElement('H1');
element.innerText = `${elementText} ${Date.now()}`;
document.body.appendChild(element);
resolve();
}, Math.random() * 2000);
});
return myPromise;
}

#TheToolBox has a nice answer for you.
Just for fun, I'm going to show you an alternative technique that uses generators that gets its inspiration from coroutines.
Promise.prototype.bind = Promise.prototype.then;
const coro = g => {
const next = x => {
let {done, value} = g.next(x);
return done ? value : value.bind(next);
}
return next();
}
Using that, your code will look like this
const addElement = elementText =>
new Promise(resolve => {
setTimeout(() => {
var element = document.createElement('H1');
element.innerText = `${elementText} ${Date.now()}`;
document.body.appendChild(element);
resolve();
}, Math.random() * 2000);
});
coro(function* () {
yield addElement('first');
yield addElement('second');
yield addElement('third');
yield addElement('fourth');
}());
There's some pretty interesting things you can do using generators with promises. They're not immediately evident here because your addElement promise doesn't resolve any actual values.
If you actually resolve some values, you could do something like
// sync
const appendChild = (x,y) => x.appendChild(y);
// sync
const createH1 = text => {
var elem = document.createElement('h1');
elem.innerText = `${text} ${Date.now()}`;
return elem;
};
// async
const delay = f =>
new Promise(resolve => {
setTimeout(() => resolve(f()), Math.random() * 2000);
});
// create generator; this time it has a name and accepts an argument
// mix and match sync/async as needed
function* renderHeadings(target) {
appendChild(target, yield delay(() => createH1('first')));
appendChild(target, yield delay(() => createH1('second')));
appendChild(target, yield delay(() => createH1('third')));
appendChild(target, yield delay(() => createH1('fourth')));
}
// run the generator; set target to document.body
coro(renderHeadings(document.body));
Worth noting, createH1 and appendChild are synchronous functions. This approach effectively allows you to chain normal functions together and blur the lines between what is sync and what is async. It also executes/behaves exactly like the code you originally posted.
So yeah, this last code example might be slightly more interesting.
Lastly,
One distinct advantage the coroutine has over the .then chaining, is that all of the resolved promises can be accessed inside the same scope.
Compare .then chains ...
op1()
.then(x => op2(x))
.then(y => op3(y)) // cannot read x here
.then(z => lastOp(z)) // cannot read x or y here
to the coroutine ...
function* () {
let x = yield op1(); // can read x
let y = yield op2(); // can read x and y here
let z = yield op3(); // can read x, y, and z here
lastOp([x,y,z]); // use all 3 values !
}
Of course there are workarounds for this using promises, but oh boy does it get ugly fast...
If you are interested in using generators in this way, I highly suggest you checkout the co project.
And here's an article, Callbacks vs Coroutines, from the creator of co, #tj.
Anyway, I hope you had fun learning about some other techniques ^__^

I am not sure why others left out a simple way out, you could simply use an array and reduce method
let promise, inputArray = ['first', 'second', 'third', 'fourth'];
promise = inputArray.reduce((p, element) => p.then(() => addElement(element)), Promise.resolve());

Your code looks close to the best you can get here. Promises can be a strange structure to get used to, especially as writing promis-ified code can often end up embedding a function in another function. As you can see here, this is a pretty common phrasing to use. There are only two stylistic changes that could possibly be made. Firstly, myPromise is unnecessary and only serves to add a confusing extra line of code. It's simpler just to return the promise directly. Secondly, you can use function binding to simplify your calls at the beginning. It may not be inside the function itself, but it does eliminate several closures. Both changes are shown below:
'use strict';
addElement("first")
.then(addElement.bind(null,"second"))
.then(addElement.bind(null,"third"))
.then(addElement.bind(null,"fourth"))
function addElement(elementText){
return new Promise(function(resolve,reject){
setTimeout(function(){
var element=document.createElement('H1');
element.innerText = `${elementText} ${Date.now()}`;
document.body.appendChild(element);
resolve();
}, Math.random() * 2000);
});
}
It's worth pointing out that, if you were willing to restructure a bit, a slightly more attractive design would take form:
'use strict';
var myWait = waitRand.bind(null,2000);
myWait
.then(addElement.bind(null, "first"))
.then(myWait)
.then(addElement.bind(null, "second"))
.then(myWait)
.then(addElement.bind(null, "third"))
function waitRand(millis) {
return new Promise((resolve, reject) => {
setTimeout(resolve, Math.random() * millis);
}
}
function addElement(elementText) {
var element = document.createElement('h1');
element.innerText = `${elementText} ${Date.now()}`;
document.body.appendChild(element);
}
This trades length of promise chain for clarity, as well as having slightly fewer nested levels.

You could simplify the use of your function by making addElement() return a function instead so it can be directly inserted into .then() handlers without having to create the anonymous function:
'use strict';
addElement("first")()
.then(addElement("second"))
.then(addElement("third"))
.then(addElement("fourth"))
function addElement(elementText){
return function() {
return new Promise(function(resolve){
setTimeout(function(){
var element=document.createElement('H1');
element.innerText = `${elementText} ${Date.now()}`;
document.body.appendChild(element);
resolve();
}, Math.random() * 2000);
});
}
}

There's not much to be done with regard to the number of closures. Nesting of functions is just something you get used to with js, and the code in the question really isn't that bad.
As others have said, writing addElement() to return a function makes for a neater main promise chain.
Going slightly further, you might consider writing the returned function with an inner promise chain, allowing the (slight) separation of promise resolution from DOM element insertion. This creates no more and no less closures, but is syntactically neater, in particular allowing you to write setTimeout(resolve, Math.random() * 2000);.
'use strict';
addElement("first")
.then(addElement("second"))
.then(addElement("third"))
.then(addElement("fourth"));
function addElement(elementText) {
return function() {
return new Promise(function(resolve, reject) {
setTimeout(resolve, Math.random() * 2000);
}).then(function() {
var element = document.createElement('H1');
document.body.appendChild(element);
element.innerText = `${elementText} ${Date.now()}`;
});
};
}
Maybe it's just me but I find this much more pleasing on the eye, albeit at the cost of an additional .then(), hence an additional promise, per addElement().
Note: If you needed to resolve the promise with a value, you are still afforded the opportunity to do so by returning a value from the chained then's callback.
Going even further, if you want the inserted elements to appear in the demanded order, not the order determined by promise settlement, then you can create/insert elements synchronously, and populate them asynchronously :
function addElement(elementText) {
var element = document.createElement('H1');
document.body.appendChild(element);
return function() {
return new Promise(function(resolve, reject) {
setTimeout(resolve, Math.random() * 2000);
}).then(function() {
element.innerText = `${elementText} ${Date.now()}`;
});
};
}
All that was necessary was to move two lines within addElement(), to change the timing of the insertions whilst leaving the element.innerText = ... line where it was. This is possible whether or not you opt for the inner promise chain.

I wrote two methods here :
Sequence = {
all( steps ) {
var promise = Promise.resolve(),
results = [];
const then = i => {
promise = promise.then( () => {
return steps[ i ]().then( value => {
results[ i ] = value;
} );
} );
};
steps.forEach( ( step, i ) => {
then( i );
} );
return promise.then( () => Promise.resolve( results ) );
},
race( steps ) {
return new Promise( ( resolve, reject ) => {
var promise = Promise.reject();
const c = i => {
promise = promise.then( value => {
resolve( value );
} ).catch( () => {
return steps[ i ]();
} );
};
steps.forEach( ( step, i ) => {
c( i );
} );
promise.catch( () => {
reject();
} );
} );
}
};
Sequence.all will run functions in a sequence until all promises in arguments are resolved. And return another Promise object with arguments as an array filled with all resolved values in sequence.
Sequence.all( [ () => {
return Promise.resolve( 'a' );
}, () => {
return Promise.resolve( 'b' );
} ] ).then( values => {
console.log( values ); // output [ 'a', 'b' ]
} );
Sequence.race will run functions in a sequence and stop running while one promise object been resolved.
Sequence.race( [ () => {
return Promise.reject( 'a' );
}, () => {
return Promise.resolve( 'b' );
}, () => {
return Promise.resolve( 'c' );
} ] ).then( values => {
console.log( values ); // output [ 'a' ]
} );

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;

OOP Javascript use Promise and problems with "this"

When designing OOP ES 5-6, where I combine prototypes with ES 6 functions. Everything is running in ElectronJS, I do not want a new version that fully supports ES 7 too, so for example the definition of import {trida} from "./cesta" must be solved require ('./ path'). My problem, but it's in Promise.
If I want to program object, I want every object to do what it has and pass the result to the main branch of the program, which contains the column of the whole procedure - object list - Content. If fun enter setTimeout (), you need to use the Promise here, which waits until the function is executed and continues to call another object.
let importing = function(){
this.name = "boot";
}
importing.prototype.start = function(resolve){
this.resolve = resolve;
setTimeout(this.test.bind(this),1000);
console.log('start -------->');
}
importing.prototype.test = function(){
this.resolve('Test');
console.log('Test -------->');
}
importing.prototype.end = function(resolve){
console.log('end -------->');
this.resolve = resolve;
this.resolve('end');
}
let geko;
let scan = new importing();
Promise.resolve(geko)
.then((geko) => new Promise(scan.start.bind(scan)))
.then((geko) => new Promise(scan.end.bind(scan)))
.catch(geko => {
console.log('Error message: ',geko)
})
Here is the problem, I do not want the features to nest in the prototype functions, I want to call every object, nicely in order, clearly. Like any book, it has its Chapter Content, and then the chapters itself and I want to have a quick entry into what I have programmed and did not look at how much a mouse was taken after a week. But for this operation, besides Promise, I also have to use the bind () function because:
importing.prototype.start = function(){
// here this acts as a window object not as importing
// why I have this object called scan.start.bind (scan)
// and here again in setTimeout i have to bind (this) to get it in this.test
// could access this importing - scan object
setTimeout(this.test.bind(this),300);
}
You would find a better way??
You should not pass methods as the argument to the new Promise constructor. If the method is asynchronous, it should return a promise by itself; if it's not asynchronous then you shouldn't be using any promises.
function importing(){
this.name = "boot";
}
importing.prototype.start = function() {
console.log('start -------->');
return new Promise(resolve => {
setTimeout(resolve, 1000); // put the native async call in here, and nothing else!
}).then(() =>
this.test()
);
};
importing.prototype.test = function() {
console.log('Test -------->');
return 'Test';
};
importing.prototype.end = function() {
console.log('end -------->');
return 'end';
}
const scan = new importing();
scan.start().then(() => scan.end()).catch(geko => {
console.log('Error message: ',geko)
});
If the whole project was created as follows:
return new Promise(resolve => {
setTimeout(resolve, 1000);
}).then(() =>
this.test()
);
I would not have much to do with the classic JS procedure, ie the nesting of functions in the functions. That's what I want to avoid. I want content, an outline, when I look at it for a year and I'm going to solve the bugs, I'll know where to start and where the mistakes are going.
let importing = function(){
this.name = "boot";
}
importing.prototype.start = function(resolve){
console.log('Start');
this.resolve = resolve;
setTimeout(this.test.bind(this),1000);
}
importing.prototype.test = function(){
console.log('Test');
this.resolve('Test');
}
importing.prototype.end = function(resolve){
console.log('End');
resolve('end');
}
let scan = new importing();
let promise = function(arg){
return new Promise((resolve, reject)=>{ // The same: new Promise(scan[arg].bind(scan))
return scan[arg].bind(scan)(resolve)
});
}
// so I would imagine chaining. Few braces, simply
// In each called object, if possible, the minimum promissory note
Promise.resolve()
.then(geko => promise('start'))
.then(geko => promise('end'))
.catch(geko => {
console.log('Error message: ',geko)
})

Javascript: horizontal chaining promises without multiple then calls

Is there a short inbuilt way to do horizontal promise chaining?
The usual way to chain steps is:
(async()=>1)().then(_=>{
//etc..
return _+_
}).then(_=>{
//etc..
return _*_
}).then(_=>{
//etc..
return alert(_) // alert 4
})
So I use a helper to avoid repeating then:
F=(...args)=>{
let p=args[0]
for(let x=1; x<args.length;++x){
p=p.then(args[x])
}
}
F((async()=>1)(), _=>{
//etc..
return _+_
}, _=>{
//etc..
return _*_
}, _=>{
//etc..
return alert(_)
})
Another variation that overloads prototype and allows for second argument of then:
Promise.prototype.thenAll = function(...args){
let p = this;
args.forEach(_=>p=p.then(_[0], _[1]))
}
;(async()=>1)().thenAll([_=>{
//etc..
return _+_
}], [_=>{
//etc..
return _*_
}], [_=>{
//etc..
return alert(_)
}])
But is there an inbuilt way to do something akin to these?
As others have said there is no built in way to do this. The following code might be of interest though.
There is a concept known as 'lifting' whereby you transform a function into one that works on wrapped values. In this case promises. It would look something like this:
const lift = (fn) => (promise) => promise.then(fn);
There is also the idea of chaining together a list of functions, composing them with one another. Like this:
const chain = (...fns) => (value) => fns.reduce((result, fn) => fn(result), value)
Together, these tools allow you to rewrite your code as:
chain(
lift(_=>++_),
lift(_=>_*=_),
lift(_=>alert(_))
)((async()=>1)())
Which alerts 4 as expected.
I'm also a little confused by your use of ++_ and _*=_ because they imply you wish to mutate the variable. Because of how your code is structured it would be a better display of intent to use _+1 and _*_
Using Array.reduce(), you can combine the series of functions into a promise chain using the following static function:
function series (initial, ...callbacks) {
return callbacks.reduce(
(chain, callback) => chain.then(callback),
Promise.resolve(initial)
)
}
series(1, _=>_+1, _=>_*_, alert)
For convenience, you could define this as Promise.series() like this:
Object.defineProperty(Promise, 'series', {
configurable: true,
value: function series (initial, ...callbacks) { ... },
writable: true
})
Lastly, in ES2017, you could alternatively use async / await to write it like this:
async function series(initial, ...callbacks) {
let value = await initial
for (const callback of callbacks) {
value = await callback(value)
}
return value
}
series(1, _=>_+1, _=>_*_, alert)
You can use rest parameter to pass N functions to a function which return a Promise to be called in sequence with the previous Promise value set to parameter passed to the current function call until no elements remain in array using async/await and repeated scheduling of the same procedure
const a = n => new Promise(resolve =>
setTimeout(resolve, Math.floor(Math.random() * 1000), ++n));
const b = n => new Promise(resolve =>
setTimeout(resolve, Math.floor(Math.random() * 1000), n * n));
const F = async(n, ...fns) => {
try {
while (fns.length) n = await fns.shift()(n);
alert(n);
} catch (err) {
throw err
}
return n
}
F(1, a, b)
.then(n => console.log(n))
.catch(err => console.error(err));
Given that the code at Question is synchronous you can use the code at Question
((_) => (_++, _*=_, alert(_)) )(1)
Or use async/await
(async(_) => (_ = await _, _++, _ *= _, alert(_)))(Promise.resolve(1))

Waiting for a promise before it exists

I am listening to user events and some of these depend on the completion of others. Dealing with these events is an asynchronous task, so I use promises.
Let's say I've got this:
A
-> B
-> C
-> ...
-> D
-> E
-> ...
-> F
My initial approach was to save a promise for A and attach the handling of B using the then method of A's promise. The same for B and C ... D and D and E ... F.
But these events can happen almost in the same time, and B can happen before A. Since I cannot listen to a promise that not exists yet... How would you resolve this?
My idea is to define an object for each level (A, B, D), where I can attach a promise and the handling of future events. When attaching a promise, I would iterate the handling of future events and set them to the promise's then. When attaching a future event, I would look if there is a promise, and attach that to it's then or save that into an array or something.
Do you know an existing solution that already solves this issue or shall I follow with my idea?
Thank you.
Further clarification as requested:
I'm listening to multimedia events in the client side that are forwarded via websocket (using socket.io) to the server. The order of the events is indeed first A, then B, etc. But since some of them happen almost at the same time, they can be processed out of order.
Example code
let promiseForA, promiseForB;
socket.on('A', data => {
promiseForA = asynchronousTaskForA();
});
socket.on('B', data => {
// What if promiseForA has not yet been defined?
promiseForA.then(neededValue => {
asynchronousTaskForB(neededValue);
});
});
socket.on('C', data => {
// What if promiseForB has not yet been defined?
promiseForB.then(neededValue => {
asynchronousTaskForC(neededValue);
});
});
socket.on('D', data => {
// What if promiseForB has not yet been defined?
promiseForB.then(neededValue => {
asynchronousTaskForD(neededValue);
});
});
function asynchronousTaskForA() {
// something
resolve('something needed by B');
}
function asynchronousTaskForB(value) {
// something with value
resolve('something needed by C ... D');
}
function asynchronousTaskForC(value) {
// something with value
resolve();
}
function asynchronousTaskForD(value) {
// something with value
resolve('something needed by E ... F');
}
My idea
So... it works. It may be an anti-pattern, wrong or insane, but... I'd like to know of a better alternative.
let PromiseWaiter = function() {
let promise = null;
let thens = [];
let self = this;
this.setPromise = function (p) {
promise = p;
thens.forEach(t => {
p.then(t);
});
};
this.then = function(t) {
if (promise === null) {
thens.push(t);
} else {
promise.then(t);
}
return self;
};
this.reset = function() {
promise = null;
thens = [];
};
};
module.exports = PromiseWaiter;
Using it:
let waitForA = new PromiseWaiter();
let waitForB = new PromiseWaiter();
let waitForD = new PromiseWaiter();
socket.on('A', data => {
waitForA.setPromise(asynchronousTaskForA());
});
socket.on('B', data => {
waitForA.then(neededValue => {
waitForB.setPromise(asynchronousTaskForB(neededValue));
});
});
socket.on('C', data => {
waitForB.then(neededValue => {
asynchronousTaskForC(neededValue);
});
});
socket.on('D', data => {
waitForB.then(neededValue => {
waitForD.setPromise(asynchronousTaskForD(neededValue));
});
});
// Note: I am confused why these functions did not return a Promise before
// They have always done that.
function asynchronousTaskForA() {
return new Promise((resolve, reject) => {
// something
resolve('something needed by B');
});
}
function asynchronousTaskForB(value) {
return new Promise((resolve, reject) => {
// something with value
resolve('something needed by C ... D');
});
}
function asynchronousTaskForC(value) {
return new Promise((resolve, reject) => {
// something with value
resolve();
});
}
function asynchronousTaskForD(value) {
return new Promise((resolve, reject) => {
// something with value
resolve('something needed by E ... F');
});
}
Thank you!
What if promiseForA has not yet been defined?
Just don't assign to it asynchronously. Create it immediately - make a promise for it. Oh, a promise for a promise is just a promise.
const A = new Promise(resolve => socket.on('A', resolve));
const B = new Promise(resolve => socket.on('B', resolve));
const C = new Promise(resolve => socket.on('C', resolve));
const D = new Promise(resolve => socket.on('D', resolve));
const afterA = A.then(asynchronousTaskForA);
const afterB = Promise.all([afterA, B]).then(asynchronousTaskForB);
const afterC = Promise.all([afterB, C]).then(asynchronousTaskForC);
const afterD = Promise.all([afterB, D]).then(asynchronousTaskForD);
My idea may be an anti-pattern, wrong or insane, but... it works.
Yeah. The problems I see with it is that it looks very much like a promise (or deferred), but isn't one:
setPromise is just resolve
then does register callbacks, but doesn't return a promise that waits for the callback result, so is not chainable
reset is not possible with promises1, but I'm not sure whether you really need this.
As I said in the comments, better don't roll your own solution but just use Promise. Of course you might write a helper function socket.getPromiseForNext('A') or so.
1: You can however use an implementation like Creed that supports cancellation. I suspect just creating a new instance that waits for the next event should suffice though.
You can create a promise in the return statement of then callback.
Example:
promiseA
.then( data => {
return new Promise((resolve, reject) => {
// do some job
});
})
As far as you are using node, you can use async/await approach:
async function startFlow() {
// here you sure create promise, instead of promise A
const res1 = await promiseA;
// here you sure create promise, instead of promise B
const res2 = await promiseB;
// etc...
}
// Start your functions execution
startFlow();

Patterns for returning same promise after calling method multiple times

Given i have a method that performs async operation and returns promise that gets resolve when that operation is done - how do i make sure that when called multiple times before that operation is done i get as a result same promise.
Edit when i call it after operation is done - i want to get new promise for current operation..
One option is to remember call ( memoize ) and eg track promise by a variable in outer scope
var p;
function test() {
if (p) return p;
p = new Promise(res) {
res();
p = null;
}
return p;
}
Any other nicer solutions?
The approach You're using here is called memoization and it's hard to come up with anything nicer.
What You might want to do is to split the tedious bits (save in outer scope, reuse, delete) from the function itself. Or even use a library:
import reusePromise from 'reuse-promise';
function test() {
return new Promise(function (res) {
res();
});
}
const reusableTest = reusePromise(test);
Full example
"use strict";
const reusePromise = require('reuse-promise').default;
let i = 0;
function test() {
return new Promise(resolve => {
setTimeout(() => resolve(i++), 100);
});
}
const reusableTest = reusePromise(test);
reusableTest().then(console.log).catch(console.err);
reusableTest().then(console.log).catch(console.err);
reusableTest().then(console.log).catch(console.err);
setTimeout(() => {
reusableTest().then(console.log).catch(console.err);
reusableTest().then(console.log).catch(console.err);
reusableTest().then(console.log).catch(console.err);
}, 200);
// output: 0 0 0 1 1 1

Categories

Resources