I am trying to use promises for a small new project. But I have some problems with understanding how I could organize my code better, with functions. Problem is, I want my functions to handle things, and my main code to handle other things. So let's see this code:
function doSomething (isGoingToResolve = true) {
return new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
}).then(response => {
console.log("in my function",response)
}).catch(error => {
console.log("in my function",error)
})
}
doSomething().then(response => {
console.log("in my main call", response)
})
With this code, the console will log in my function something and in my main call undefined: https://jsfiddle.net/hkacvw2g/
So I found two solutions to solve my problem, but I just don't like them:
The first one is to create a variable, use a .then() on the variable, and then return the variable (https://jsfiddle.net/hkacvw2g/1/):
function doSomething (isGoingToResolve = true) {
let promise = new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
})
promise.then(response => {
console.log("in my function",response)
}).catch(error => {
console.log("in my function",error)
})
return promise
}
And the second solution (https://jsfiddle.net/hkacvw2g/2/):
function doSomething (isGoingToResolve = true) {
return new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
}).then(response => {
console.log("in my function",response)
return new Promise(resolve => {
resolve(response)
})
}).catch(error => {
console.log("in my function",error)
return new Promise((resolve,reject) => {
reject(error)
})
})
}
Am I missing a better solution to solve my problem?
You can simply return the value (or re-throw the error) and it will be resolved in promise chain:
function doSomething (isGoingToResolve = true) {
return new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
}).then(response => {
console.log("in my function",response)
return response;
}).catch(error => {
console.log("in my function",error)
throw error;
})
}
You might not want that throw error, it depends on how you want to handle your rejections. This way when you re-throw the error, you should catch it when calling the doSomething() method.
You can write a tap function, to tap into a promise chain, and do something while passing along the result, and a parallel tapCatch function, to tap into errors while rethrowing them:
const tap = fn => value => { fn(value); return value; };
const tapCatch = fn => reason => { fn(reason); throw reason; };
Now you can write your code as:
function doSomething(isGoingToResolve = true) {
(isGoingToResolve ?
Promise.resolve("something") :
Promise.reject("something else")
)
.then( tap (response => console.log("in my function", response)))
.catch(tapCatch(error => console.log("in my function", error)));
}
doSomething()
.then(response => console.log("in my main call", response));
However, actually your first solution is better. It reduces the risk of messing up the promise chain by inadvertently forgetting to, or not realizing that you have to, return the original value in then clauses, or rethrow in catch clauses which such clauses are meant only for logging purposes or other side-effects.
You could also pollute the Promise prototype with something like tap (we'll call it thenDo), making it a bit easier to use:
Promise.prototype.thenDo = function(ifFulfilled, ifRejected) {
return this.then(
value => { ifFulfilled(value); return value; },
reason => { ifRejected(reason); throw reason; });
};
Promise.prototype.catchDo = function(ifRejected) {
return this.catch(reason => { ifRejected(reason); throw reason; });
};
Now you can write
function doSomething(isGoingToResolve = true) {
(isGoingToResolve ?
Promise.resolve("something") :
Promise.reject("something else")
)
.thenDo (response => console.log("in my function", response))
.catchDo(error => console.log("in my function", error));
}
Related
I have a function that looks like following
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
return true;
} else {
console.log("studio not available");
return false;
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
};
however the return statements don't return anything when I use the function in the following manner
useEffect(() => {
const agent = checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("studio available is: ", agent);
}, []);
the console.log massages appear but the return statement is undefined.
any help would be appreciated.
You can not return from a callback function, as it is running asynchronously and you are not waiting for it to have a result ready.
You can however make the function itself async by returning a Promise instead of the actual result and wait until the Promise has a result ready (e.g. it is resolved):
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
reject(); // reject on failure
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
resolve(true); // resolve instead of return
} else {
console.log("studio not available");
resolve(false);
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
).then((agent) => { // then callback is called when the promise resolved
console.log("studio available is: ", agent);
}).catch(error => { // catch is called when promise got rejected
console.log('An error happened');
});
}, []);
The function servceInfo.OnServiceStateChange is a function into the object (seems to be an event).
I'd suggest declaring a variable on the checkForAvailableAgent like connected and change it's value when the event is called.
Then access it using checkForAvailableAgent.connected.
A version with async/await and try/catch
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId,
OnError: reject,
OnServiceStateChange: e => resolve(e.ConnectedAdvisers > 0)
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
(async () => {
try {
const isAvailable = await checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("Result", isAvailable)
} catch(e) {
console.error(e)
}
})()
// console.log("studio available is: ", agent);
}, []);
There are 2 possible reasons
you are not returning anything from checkForAvailableAgent.
After returning from the checkForAvailableAgent, it might be asynchronous function. You can use async & await.
I am trying to user webgazer.js where my code basically checks to see whether the webgazer is initialized and when it is initialized it resolves a promise which dispatches an action. This works however if for example there is no webcam I need to throw an error. The error in my code never gets called.
Here is my code
export function detectJsCamera() {
return async(dispatch) => {
dispatch({type: types.JS_DETECTING_CAMERA});
try {
await detectCamera();
await dispatch({type: types.JS_CAMERA_DETECTED});
} catch (error) {
await dispatch({type: types.CAMERA_DETECTION_FAILED, error: error.message});
throw error;
// this.props.history.push('/setup/positioning')
};
}
}
const detectCamera = () => new Promise((resolve, reject) => {
const checkIfReady = () => {
if (webgazer.isReady()) {
resolve('success');
} else {
console.log('called')
setTimeout(checkIfReady, 100);
}
}
setTimeout(checkIfReady,100);
});
You will need to reject in order to throw an exception like below
const detectCamera = () => new Promise((resolve, reject) => {
const checkIfReady = () => {
if (webgazer.isReady()) {
resolve('success');
} else {
console.log('called');
reject("some error");
}
}
setTimeout(checkIfReady,100);
});
You need to call reject() in your detectCamera method when your webgazer is not initialised then it would be caught in your catch block in detectJsCamera method.
I use the following code to return promise which is working OK.
The promise return the data value
run: () => {
return new Promise((resolve, reject) => {
....
}).then((data) => {
let loginApi = data[0]
let test = 1;
}).catch((err) => {
if (err.statusCode === 302) {
var data = url.parse(err.response.headers.location, true)
resolve(data )
}
})
});
I call it
module.run()
.then((data) => {
And I was able to get the data.
now I want to return also value test in the resolve, how should I do it?
I try to add it like this
resolve({data,test});
resolve([data,test]);
with call like
module.run()
.then({data,test}) => {
without success(test is empty), I read about spread but this is the only option?
I use ES6 with bluebird latest version
If you are using promise chain, in promise chain you have then->then->catch->... format. Always return Promise.resolve or Promise.reject. Promise.resolve will give success result for next then block and Promise.reject will go to next catch block.
var module = {
run: () => {
return new Promise((resolve, reject) => {
// ....
resolve('promise resolved')
}).then((data) => {
let loginApi = data[0]
let test = 1;
return Promise.resolve({data,test})
}).catch((err) => {
if (err.statusCode === 302) {
var data = url.parse(err.response.headers.location, true)
return Promise.resolve({data, test});
}
return Promise.reject(err);
})
}
};
module.run().then(({data, test}) => {
console.log(data, test);
})
I am trying to create a realm helper function to return an open and writeable realm instance...I am not sure why this is not working? I am getting error:
Error: Can only delete objects within a transaction.
This indicates that the (realm) in the resolve is closing/becoming unwritable when it is returned via the resolve.
import * as Realm from "realm";
// Realm object is returned but is not ready for writing?.
export const RealmHelper = {
realmWrite(schema: any) : Promise<Realm> {
const promise = new Promise<Realm>((resolve, reject) => {
Realm.open({schema: schema}).then((realm: Realm) => {
realm.write(() => {
resolve(realm);
});
}).catch((error) => reject(error));
});
return promise;
}
};
I am calling the helper with:
return RealmHelper.realmWrite([UserSchema]).then((realm: Realm) => {
realm.delete(this); // from within a UserSchema
return true;
});
This more ugly and verbose way works fine:
function deleteUser() {
const promise = new Promise<boolean>((resolve, reject) => {
Realm.open({schema: [UserSchema]}).then((realm: Realm) => {
realm.write(() => {
console.log("deleting: user ", this);
realm.delete(this);
resolve(true);
});
}).catch((error) => reject(error));
});
return promise;
}
When you resolve the promise in this block
Realm.open({schema: schema}).then((realm: Realm) => {
realm.write(() => {
resolve(realm);
});
}).catch((error) => reject(error));
you're also immediately exiting the scope in which the write transaction happen. Consider this:
Realm.open({schema: schema}).then((realm: Realm) => {
realm.write(() => {
console.log("before resolve");
resolve(realm);
console.log("after resolve");
});
}).catch((error) => reject(error));
// use the helper
return RealmHelper.realmWrite([UserSchema]).then((realm: Realm) => {
console.log("before delete");
realm.delete(this); // from within a UserSchema
console.log("after delete");
return true;
});
// OUTPUT:
before resolve
after resolve
before delete
after delete
I haven't tested this code, but I expect you'll see something like it.
Fixed by adding a callback that would be called before the resolve in the write callback. Here's the solution that works for me:
export const RealmHelper = {
realmWrite<T>(schema: any, func: (realm: Realm) => T) : Promise<T> {
const promise = new Promise<T>((resolve, reject) => {
Realm.open({schema: schema}).then((realm: Realm) => {
realm.write(() => {
resolve(func(realm));
});
}).catch((error) => reject(error));
});
return promise;
}
};
Can be called using:
RealmHelper.realmWrite([UserSchema], (realm) => realm.create<User>("User", this));
I try to refacter code from using callback to Promise but I got some weird behavior from Promise(bluebird)
here is the main logic
function func1(callback) {
func2()
.then(function(resultFromFunc2) {
if (resultFromFunc2 === true) {
callback(null, resultFromFunc2)
} else {
return func3()
}
})
.then(function(resultFromFunc3) {
console.log('Func 3', resultFromFunc3)
callback(null, resultFromFunc3)
})
.catch(function(err) {
console.log('error', err)
})
}
func1(function(err, result) {
console.log('func1', err, result);
})
and in func2 and func3 is just a simple resolve
function func2() {
return new Promise((resolve, reject) => {
resolve(true)
});
}
function func3() {
return new Promise((resolve, reject) => {
resolve(true)
});
}
If func2 resolve true code should stop running in the first then, but I found the second then is called
here is result from terminal.
func1 null true
Func 3 undefined
func1 null undefined
How could I stop calling the second then when func2 is resolve true
#Phattahana,
Just posting my thoughts on your question.
As you have said in your answer, you shouldn't be calling callback() from the 1st then. Or else you should be ready to change the workflow and remove the 2nd then. there are lot many possibilities.
In a specific scenario, where you want the code to be executed just like the question you posted (ie; if the if condition is true, donot call 2nd then - scenario), you can have your code like below.
var Promise = require("bluebird");
function func1(callback) {
func2()
.then((resultFromFunc2) => {
return new Promise((resolve, reject) => {
if (resultFromFunc2 === true) {
callback(null, resultFromFunc2);
} else {
resolve(func3());
}
});
}).then((resultFromFunc3) => {
console.log('Func 3', resultFromFunc3)
callback(null, resultFromFunc3)
}).catch((err) => {
console.log('error', err)
});
}
func1((err, result) => {
console.log('func1', err, result);
return 1;
});
function func2() {
return new Promise((resolve, reject) => {
resolve(false)
});
}
function func3() {
return new Promise((resolve, reject) => {
resolve(true)
});
}
The code inside the 1st then should be made into a separate promise resolving function and should be resolving it only if the condition is not met.
Hope its clear.
Thanks,
The problem here is with your second then section. If you remove it, everything should work as expected (if I understand correctly). See the snippet below - if you change func2 to resolve(false), func3 will be called, otherwise the callback will be called.
function func1(callback) {
func2()
.then(function(resultFromFunc2) {
if (resultFromFunc2 === true) {
callback(null, resultFromFunc2)
} else {
return func3()
}
})
.catch(function(err) {
console.log('error', err)
})
}
func1(function(err, result) {
console.log('func1', err, result);
})
function func2() {
return new Promise((resolve, reject) => {
console.log('func2');
resolve(true)
});
}
function func3() {
return new Promise((resolve, reject) => {
console.log('func3')
resolve(true)
});
}
I just found the solution,
function func1(callback) {
func2()
.then(function(resultFromFunc2) {
if (resultFromFunc2 === true) {
return 'result from func2'
} else {
return func3()
}
})
.then(function(result) {
callback(null, result)
})
.catch(function(err) {
console.log('error', err)
})
}
I should not callback in the first then because whatever return from first then it send to the second then.
I miss understand behavior of Promise. first I think it will not call the second then if the first then doesn't return Promise.