Using promises with event emitters - javascript

I want promisify event emitter in an node.js XMPP client, this is my code:
this.watiForEvent = function (eventEmiter, type) {
return new Promise(function (resolve) {
eventEmiter.on(type, resolve);
})
};
this._response = function () {
return this.watiForEvent(this.client, 'stanza');
};
in above code I promisify the XMPP stanza event emitter and use it something like this
CcsClient.prototype.responseHandler = function () {
var _this = this;
return _this._response().then(function (stanza) {
_this.responseLimit--;
if (stanza.is('message') && stanza.attrs.type !== 'error') {
var messageData = JSON.parse(stanza.getChildText("gcm"));
switch (messageData.message_type) {
case 'control':
if (messageData.control_type === 'CONNECTION_DRAINING') {
_this.draining = true;
}
return;
case 'ack':
return {
messageId: messageData.message_id,
from: messageData.from
};
gcm.responseHandler().then(function (options) {
console.log(options);
}).
catch (function (error) {
console.log(error);
});
My problem is stanza event just called one time. Maybe promise is not good?

Es6-Promises have 3 states, pending, resolved and rejected.
In the code you provided eventEmitter is resolving the promise multiple times.
eventEmiter.on(type, resolve);
Thats is not going to work, because once a promise is rejected or resolved its state can not be changed, you can not resolve a promise multiple times.
I suggest using the observable pattern:
function observable(){
var cbs = { }; // callbacks;
return {
on: function(type,cb) {
cbs[type] = cbs[type] || [];
cbs[type].push(cb);
},
fire: function(type){
var arr = cbs[type] || [];
var args = [].slice.call(arguments,1);
arr.forEach(function(cb){
cb.apply(this,args);
});
}
}
}

Related

Issue while Implementing Promise class with JavaScript

I'm trying to implement Promise Class with JavaScript (currently only with resolve, no reject).
For some reason, when I use it without setTimeout around the resolve function, I get double console.logs from the 'then' methods , and otherwise it works fine.
That's my code
const STATUSES = {
PENDING: 'PENDING',
RESOLVED: 'RESOLVED',
REJECTED: 'REJECTED',
}
class CustomPromise {
#value = null
#status = STATUSES.PENDING
#thenCallbacks = []
constructor(cb) {
cb(this.#resolve)
}
#updateState = (status, value) => {
this.#status = status
this.#value = value
}
#resolve = (value) => {
this.#updateState(STATUSES.RESOLVED, value)
this.#thenCallbacks.reduce((acc, callback) => callback(acc), this.#value)
}
then = (cb) => {
this.#thenCallbacks.push(cb)
if (this.#status === STATUSES.RESOLVED) {
this.#thenCallbacks.reduce((acc, callback) => callback(acc), this.#value)
}
return this
}
}
new CustomPromise((resolve) => {
resolve(1)
})
.then((value) => {
console.log('first value', value)
return 2
})
.then((value) => {
console.log('second value', value)
return null
})
the result:
Wrapping the 'resolve' like so makes the code run as expected:
setTimeout(() => {
resolve(1)
}, [1000])
thanks!
The problem is in the then method: when you find that the promise already resolved, you should only call the newly provided callback -- not the others. In this case the other callbacks would already have been called.
Some other comments:
There is no accumulation like you suggest with reduce. All callbacks should be treated equally, each getting the value as argument.
It is not allowed for a promise to resolve more than once, so you should ignore such a request when you find that the promise status is no longer pending.
Here is a correction for the double output you got:
const STATUSES = {
PENDING: 'PENDING',
RESOLVED: 'RESOLVED',
REJECTED: 'REJECTED',
}
class CustomPromise {
#value = null;
#status = STATUSES.PENDING;
#thenCallbacks = [];
constructor(cb) {
cb(this.#resolve);
}
#updateState = (status, value) => {
this.#status = status;
this.#value = value;
}
#resolve = (value) => {
if (this.#status !== STATUSES.PENDING) return; // Ignore!
this.#updateState(STATUSES.RESOLVED, value);
this.#thenCallbacks.forEach(callback => callback(value)); // No accumulation
}
then = (cb) => {
this.#thenCallbacks.push(cb)
if (this.#status === STATUSES.RESOLVED) {
cb(this.#value); // Only this one. The other callbacks were already called
}
return this;
}
}
new CustomPromise((resolve) => {
resolve(1);
})
.then((value) => {
console.log('first value', value);
return 2;
})
.then((value) => {
console.log('second value', value);
return null;
})
Note that you have much more work to do, including:
then callbacks should be called asynchronously.
then should return a new promise. Because this is not happening in your code, and a promise value cannot be changed, the output is now always the same value.
Promises should implement the promise resolution procedure.
You probably want to test your implementation against the Promises/A+ Compliance Test Suite. This way you can gradually improve your implementation until it passes all tests. Be warned... there is a long road ahead, but worth the experience.
To completely fix your code so it complies with Promises/A+ specification, a lot of change is required to your code. As I once did this effort of implementing a compliant implementation, I just provide you with the class version of what I provided in this answer:
class CustomPromise {
static Deferred = class {
constructor() {
this.state = 'pending';
this.value = undefined;
this.consumers = [];
this.promise = Object.create(CustomPromise.prototype, {
then: { value: this.then.bind(this) }
});
}
// 2.1.1.1: provide only two ways to transition
fulfill(value) {
if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
this.state = 'fulfilled'; // 2.1.1.1: can transition
this.value = value; // 2.1.2.2: must have a value
this.broadcast();
}
reject(reason) {
if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
this.state = 'rejected'; // 2.1.1.1: can transition
this.value = reason; // 2.1.3.2: must have a reason
this.broadcast();
}
// A promise’s then method accepts two arguments:
then(onFulfilled, onRejected) {
var consumer = new CustomPromise.Deferred();
// 2.2.1.1 ignore onFulfilled if not a function
consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
// 2.2.1.2 ignore onRejected if not a function
consumer.onRejected = typeof onRejected === 'function' ? onRejected : null;
// 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise
this.consumers.push(consumer);
// It might be that the promise was already resolved...
this.broadcast();
// 2.2.7: .then() must return a promise
return consumer.promise;
}
broadcast() {
var promise = this;
// 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved
if (this.state === 'pending') return;
// 2.2.6.1, 2.2.6.2 all respective callbacks must execute
var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected';
var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject';
// 2.2.4 onFulfilled/onRejected must be called asynchronously
setTimeout(function() {
// 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once
promise.consumers.splice(0).forEach(function(consumer) {
try {
var callback = consumer[callbackName];
// 2.2.1.1, 2.2.1.2 ignore callback if not a function, else
// 2.2.5 call callback as plain function without context
if (callback) {
// 2.2.7.1. execute the Promise Resolution Procedure:
consumer.resolve(callback(promise.value));
} else {
// 2.2.7.3 resolve in same way as current promise
consumer[resolver](promise.value);
}
} catch (e) {
// 2.2.7.2
consumer.reject(e);
};
})
});
}
// The Promise Resolution Procedure: will treat values that are thenables/promises
// and will eventually call either fulfill or reject/throw.
resolve(x) {
var wasCalled, then;
// 2.3.1
if (this.promise === x) {
throw new TypeError('Circular reference: promise value is promise itself');
}
// 2.3.2
if (x instanceof CustomPromise) {
// 2.3.2.1, 2.3.2.2, 2.3.2.3
x.then(this.resolve.bind(this), this.reject.bind(this));
} else if (x === Object(x)) { // 2.3.3
try {
// 2.3.3.1
then = x.then;
if (typeof then === 'function') {
// 2.3.3.3
then.call(x, function resolve(y) {
// 2.3.3.3.3 don't allow multiple calls
if (wasCalled) return;
wasCalled = true;
// 2.3.3.3.1 recurse
this.resolve(y);
}.bind(this), function reject(reasonY) {
// 2.3.3.3.3 don't allow multiple calls
if (wasCalled) return;
wasCalled = true;
// 2.3.3.3.2
this.reject(reasonY);
}.bind(this));
} else {
// 2.3.3.4
this.fulfill(x);
}
} catch(e) {
// 2.3.3.3.4.1 ignore if call was made
if (wasCalled) return;
// 2.3.3.2 or 2.3.3.3.4.2
this.reject(e);
}
} else {
// 2.3.4
this.fulfill(x);
}
}
}
constructor(executor) {
// A Promise is just a wrapper around a Deferred, exposing only the `then`
// method, while `resolve` and `reject` are available in the constructor callback
var df = new CustomPromise.Deferred();
// Provide access to the `resolve` and `reject` methods via the callback
executor(df.resolve.bind(df), df.reject.bind(df));
return df.promise;
}
}
new CustomPromise((resolve) => {
resolve(1);
})
.then((value) => {
console.log('first value', value);
return 2;
})
.then((value) => {
console.log('second value', value);
return null;
});

jasmine test that function does return a resolved promise

Code:
var cartModule = (function() {
checkOverbook: function(skip) {
return new Promise(function(resolve, reject) {
if (skip) {
addItemPromiseResolver = resolve;
} else {
resolve({"continue_add":true})
}
})
},
})();
I'd like to test that when cartModule.checkOverbook is called with skip = true, that the promise is resolved, but that when it's called with skip = false it is not resolved. Is this possible/recommended, or should I just test this in the context of the consuming function?
I think it is possible, you could possibly do something like so:
it('resolves if skip is false', async () => {
const skip = false;
await expectAsync(cartModule.checkOverbook(skip)).toBeResolvedTo({ 'continue_add': true });
});
it('changes addItemPromiseResolver to resolve if skip is true', () => {
expect(addItemPromiseResolver).toBeUndefined();
const skip = true;
// just call the function
cartModule.checkOverbook(skip);
expect(addItemPromiseResolver).not.toBeUndefined();
});

named promise chainy for normal promises that handle events

I am trying to create a named promise chain. I am not sure of how to achieve this. The goal is following:
function multiplepromises() {
var prom = function (resolve, reject) {
var lifecycleeventone = new someEvent();
var lifecycleeventtwo = new someEvent();
var lifecycleeventthree = new someEvent();
var lifecycleeventfour = new someEvent();
var lifecycleeventfive = new someEvent();
lifecycleeventone.on(function () {
try {
resolve("eventone")
} catch {
reject("eventone")
}
})
lifecycleeventtwo.on(function () {
try {
resolve("eventtwo")
} catch {
reject("eventtwo")
}
})
lifecycleeventthree.on(function () {
try {
resolve("eventthree")
} catch {
reject("eventthree")
}
})
lifecycleeventfour.on(function () {
try {
resolve("eventfour")
} catch {
reject("eventfour")
}
})
lifecycleeventfive.on(function () {
try {
resolve("eventfive")
} catch {
reject("eventfive")
}
})
maineventlikefinallySOcalledalways.on(function(){
try {
resolve("maineventlikefinallySOcalledalways")
} catch {
reject("maineventlikefinallySOcalledalways")
}
})
}
return prom
}
multiplepromises()
.onlifecycleeventoneAsProm((result)=> result) //eventone promise resolve
.onlifecycleeventoneerrorAsProm((error)=> error) //eventone
.onlifecycleeventtwoAsProm((result)=> result) //eventtwo promise resolve
.onlifecycleeventtwoerrorAsProm((error)=> error) //eventtwo
.onlifecycleeventthreeAsProm((result)=> result) //eventthree promise resolve
.onlifecycleeventthreeerrorAsProm((error)=> error) //eventthree
.onlifecycleeventfourAsProm((result)=> result) //eventfour promise resolve
.onlifecycleeventfourerrorAsProm((error)=> error) //eventfour
.onlifecycleeventfiveAsProm((result)=> result) // eventfive promise resolve
.onlifecycleeventfiveerrorAsProm((error)=> error) //eventfive
.then((result)=> result) // maineventlikefinallySOcalledalways promise resolve
.error((error)=> error) // maineventlikefinallySOcalledalways promise reject
multiplepromises()
.onlifecycleeventoneAsProm((result)=> result) //eventone promise resolve
.onlifecycleeventoneerrorAsProm((error)=> error) //eventone
.onlifecycleeventtwoAsProm((result)=> result) //eventtwo promise resolve
.onlifecycleeventtwoerrorAsProm((error)=> error) //eventtwo
.onlifecycleeventthreeAsProm((result)=> console.log("test"))
// lifecycleeventthree promise reject stops here and
// doesnt continue to .then/.error since there was no return from this lifecycle event(promise)
I have read this and this doesnt solve the purpose completely.
Handling multiple catches in promise chain and https://javascript.info/promise-chaining
Dont want to use Rx and want to keep to vanilla js
You can‘t achieve something like that with Promises.
Instead you can make a function that returns an object with event registrar functions, which return again the object.
Here is a simple example:
function test() {
return new (function() {
this.eh = {};
this.on = (event, handler) => {
this.eh[event] = handler;
return this;
}
this.call = (event, ...args) => {
if (typeof this.eh[event] === 'function') {
this.eh[event](...args);
}
}
Promise.resolve().then(() => {
// Do your stuff...
// Example:
this.call('msg', 'This is a message.');
setTimeout(() => {
this.call('some-event', 'This is some event data.');
this.call('error', 'This is an error.');
}, 1000);
});
})()
}
test()
.on('msg', (msg) => console.log(`Message: ${msg}`))
.on('some-event', (data) => console.log(`Some event: ${data}`))
.on('error', (err) => console.log(`Error: ${err}`))
I hope that‘s what you were up to.
Edit:
Here's another attempt: https://jsfiddle.net/bg7oyxau/

Rejecting a promise from inside a callback

I've got a Promise function
function test()
{
let promise = new Promise<string>(function(resolve, reject)
{
func( resolve, reject );
});
return promise;
}
function func(resolve, reject )
{
let reject2 = reject;
window.on( "error", (msg) =>
{
console.log("inside error");
reject2();
} );
// other stuff
}
In an error condition - I'm trying to reject the original promise.
It seems to work - but evey time I create the error condition, the "inside error" message gets printed that many times.
Thanks.
I think you want to remove the handler after it fires.
Something like this:
function test()
{
let promise = new Promise<string>(function(resolve, reject)
{
func( resolve, reject );
});
return promise;
}
function func(resolve, reject )
{
let reject2 = reject;
let handler = (msg) =>
{
console.log("inside error");
reject2();
window.off( "error", handler ); //removing the handler here
}
window.on( "error", handler);
// other stuff
}
But this approach has the problem that will remove all registered handlers at any error and I don't know if this is desired.
I don't know what you want to do, my guess was too long to put in comments, so give this a shot. Quite sure this is an A->B->C problem.
!function(){
let errorAttached = false,
currentReject = null;
function test()
{
let promise = new Promise(function(resolve, reject)
{
func( resolve, reject );
});
return promise;
}
function func(resolve, reject )
{
let currentReject = reject;
if(!errorAttached) {
window.on( "error", (msg) =>
{
console.log("inside error");
currentReject();
});
errorAttached = true;
}
// other stuff
}
}();
I did not understand why you are creating a callback function to treat the result of your promise. I've never used this way. Perhaps you want to cancel your promise when something happens to the page. In this case, you could follow the example on this page. You will have a bunch of way to implement it.
https://blog.bloomca.me/2017/12/04/how-to-cancel-your-promise.html
class CancelablePromise {
constructor(executor) {
let _reject = null;
const cancelablePromise = new Promise((resolve, reject) => {
_reject = reject;
return executor(resolve, reject);
});
cancelablePromise.cancel = _reject;
return cancelablePromise;
}
}
// now lets work with the class
var cancelable = false;
const p = new CancelablePromise((resolve, reject) => {
setTimeout(() => {
if(!cancelable)
resolve(console.log('resolved!'));
}, 2000);
})
p.catch(error => console.log(error.toString()));
setTimeout(() => {
cancelable = true;
p.cancel(new Error("Whoops!"));
}, 1000); // change by 3000 to be resolved

Chain promises - returned promise resolve does not cause then method to be invoked

I am trying to chain promises but for some reason, my then method is not being called - can not find the reason why, assistance would be much appreciated.
The then - .then((decodedTokenData) is not being called but instead the entire promise returned.
let handler = async (event) => {
let accessToken = undefined;
let targetGroup = undefined;
const promise = new Promise(() => {
accessToken = event.header.authorization;
targetGroup = event.body.targetGroup;
let promise = decodeAndVerifyToken(accessToken);
return promise;
}).then((decodedTokenData) => {
let isNotGroupMemberResult = isNotGroupMember(decodedTokenData.userGroups);
return setUserGroup(result.userGroups, targetGroup, userPoolId);
}).then(() => {
return true;
});
return promise;
}
let decodeAndVerifyToken = (jwtToken) => {
return new Promise((resolve, reject) => {
jwt.verify(jwtToken, pem, function (err, dToken) {
if (err) {
reject(err);
}
let userGroups = dToken && dToken["cognito:groups"] ? dToken["cognito:groups"] : [];
let username = dToken && dToken.username ? dToken.username : null;
let retval = {
userGroups,
username,
decodedToken: dToken
}
resolve(retval);
})
});
}
let isNotGroupMember = (userGroups) => {
let groupIndex = userGroups.findIndex(groupName => groupName.include(reviewersGroup) || groupName.include(ownersGroup));
let isNotGroupMemberRetval = groupIndex == -1;
return isNotGroupMemberRetval;
}
let setUserGroup = (username, groupname, userPoolId) => {
return new Prmoise((resove, reject) => {
var params = {
GroupName: groupname,
UserPoolId: userPoolId,
Username: username
};
cognitoidentityserviceprovider.adminAddUserToGroup(params, function (err, data) {
if (err) {
reject(err);
} else
resolve(data);
});
});
};
EDIT :
I understood my problem... I read the following documentation on Mozilla promises :
Once a Promise is fulfilled or rejected, the respective handler function (onFulfilled or onRejected) will be called asynchronously (scheduled in the current thread loop). The behaviour of the handler function follows a specific set of rules. If a handler function:
returns a value, the promise returned by then gets resolved with the returned value as its value.
And I thought it applies to both promises and the then method - but it applies only to the then method ...
I'm not sure I understood right what are you doing here, but let me try.
When you do new Promise(() => ...whatever... you create a promise that never resolves, so .then() is never executed. The proper way to create a promise that resolves to some result is new Promise((res) => res(result)).
Example in node:
> const p1 = new Promise(() => 1).then(console.log);
undefined
> p1
Promise { <pending> }
> const p2 = new Promise(res => res(1)).then(console.log);
undefined
> 1
new Promise passes resolve and reject to the callback its passed, but you are not making use of it here:
const promise = new Promise(() => {
accessToken = event.header.authorization;
targetGroup = event.body.targetGroup;
let promise = decodeAndVerifyToken(accessToken);
return promise;
})
This creates a promise that is never resolved. There is no need for new Promise here. Just call decodeAndVerifyToken directly:
decodeAndVerifyToken(event.header.authorization)
.then((decodedTokenData) => { ... })
...

Categories