get callback arguments from sinon.spy inside promise javascript - javascript

I'm running a test using mocha and sinon to get a callback value from inside a promise scope of HTTP-request and it doesn't work due to the async nature of promises. It's because by the time sinon.spy checks on callback, It would have been vanished already and become empty or undefined. Here's the testing code:
it('should issue GET /messages ', function() {
server.respondWith('GET', `${apiUrl}/messages?counter=0`, JSON.stringify([]));
let callback = sinon.spy();
Babble.getMessages(0, callback);
server.respond();
sinon.assert.calledWith(callback, []);
});
and the promise:
function requestPoll(props) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(props.method, props.action);
xhr.timeout = 500; // time in milliseconds
if (props.method === 'post' ) {
xhr.setRequestHeader('Content-Type', 'application/json');
}
xhr.addEventListener('load', function(e) {
resolve(e.target.responseText);
});
xhr.send(JSON.stringify(props.data));
});
}
and the call which I'm trying to get callback from on sinon.spy
getMessages: function(counter, callback){
requestPoll({
method: "GET",
action: "http://localhost:9090/messages?counter="+counter
}).then(function(result){
callback(result);
});
}
sinon.spy says it didn't have any arguments (due to async functionality). I tried to look for a way to get result outside the scope and put it on callback.yet I found out it was impossible. also tried resolve and promise return but didn't succeed.
How can I make this unit test pass?
Edit:
this is my attempt:
getMessages: function(counter, callback){
var res;
res = httpRequestAsync("GET",'',"http://localhost:9097/messages?counter=",counter);
console.log(res);
if(res!="")
callback( JSON.parse(res) );
}
I put the request in a separate function:
function httpRequestAsync(method,data,theUrl,counter)
{
return requestPoll({
method: method,
action: theUrl+counter,
data: data
}).then(JSON.parse);
}
It returned res as the promise and inside its prototype there's the promised value I need.
How can I access that promised value over there?

I recommend you not to mix promises and callbacks. If you already have promise based function stick with it
First make getMessages not to break promise chaing. Make it return a Promise
getMessages: function(counter) {
return requestPoll({
method: "GET",
action: "http://localhost:9090/messages?counter=" + counter
}).then(JSON.parse)
}
Then use this promise in your test
it('should issue GET /messages ', function() {
server.respondWith('GET', `${apiUrl}/messages?counter=0`, JSON.stringify([{testdata}]));
const gettingMessages = Babble.getMessages(0);
server.respond();
// return a promise so testing framework knows the test is async
return gettingMessages.then(function(messages) {
// make assertion messages is actually testdata
})
})

Related

How to execute asynchronous code in Javascript

I've looked into MDN web docs article on async functions and for some reason it doesn't work for me
Here's my code
function createObject() {
try {
console.log("Processing POST Loan.");
var data = {
"value1" : 1288,
"value2" : [{
"value3" : 3833,
"value4": [{
"value5": new Date()
}]
}]
}
var auth = Buffer.from("login:pass").toString('base64')
const res = request("http://url.com/resource", {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Authorization': "Basic "+auth
},
body: JSON.stringify(data)
}, function(error, response, body){
//console.log(response + ' '+body)
})
var response = res.body.id;
return response
}
catch(err) {
throw err
}
}
async function uploadReport() {
console.log('cerate object')
var objectId = await createObject();
console.log('object = '+objectId)
}
uploadReport()
Exactly as described in the article. But when I run my script I get this result:
cerate object
Processing POST Loan.
'data {"value1":1288,"value2":[{"value3":3833,"value4":[{"value5":"2021-10-05T09:45:46.126Z"}]}]}'
'auth '
object = undefined
Then nothing happens for a few seconds and execution stops. Http request works fine and objects are being created as I run the script, though I don't get any response (this API should return auto generated object ID). What am I doing wrong here?
I'm assuming you are quite new to JS programming and it seems you lack a little understanding about how the flow of execution works for async code.
In JS, async code works in promises. Promises are the way JS represents code that eventually will yield a value (or void, but it's the same).
So now we need a way to control the flow of execution. This means, we should be able to execute code that depends on that result after we have gotten that result. Or in other words, we should wait for the promise to resolve and then execute the code that depended on it.
Enter callbacks. Callbacks is how this is done in JS (before async/await appeared, but let's not get ahead of ourselves). As functions are first class citizens in JS we can declare them and pass them around as function arguments. So, in those terms, a callback it's the code that should be executed after the promise has given us its result.
When it comes to callbacks, me personally I have seen two ways of dealing with them.
A first approach, function expects such callback as an argument. Usually (but it doesn't have to be like this) the first arguments are the actual arguments necessary to perform the task, and the last one is the callback: what to do after the task is done. Example:
// A function that receives an `arguments` object and a `callback` function.
// Observe
function doSomethingAsynchronous(arguments, callback) {
const a = arguments.a
const b = arguments.b
const result = a + b //this is not asynchronous, only to illustrate.
callback(result)
}
You would use this function like this:
doSomethingAsynchronous({a:2, b:3}, (result)=>{
console.log(`The result was ${result}`)
})
Note how doSomethingAsynchronous does not return anything; the flow of execution is directed towards the callback.
A second approach might be a function that returns an actual Promise. Promises have a then() method and a catch() method. These are used to chain more code after the resolution of the Promise:
function iReturnAPromise(arguments) {
return new Promise((resolve, reject) => {
const result = arguments.a + arguments.b;
resolve(result);
})
}
You would manage the flow of execution by doing so:
const promiseOfTheResult = iReturnAPromise({a: 2, b:2})
promiseOfTheResult.then((result) => {console.log(result)})
// You'll never see it like that. You'll always see:
iReturnAPromise({a:2, b:3}).then((result) => {console.log(result)})
And last, but definitely not least, came async/await which simplified the use of promises. With async await, you would declare iReturnAPromise just the same, but you would use it like so;
const result = await iReturnAPromise({a:1, b:2})
console.log(result)
Notice how this last method keeps the code in line and avoids the callback hell. Also notice how functions that don't return a promise cannot be awaited, they first have to be "promisified": that is, wrapping them in a promise.
Let's start from the begining since you seem a little confused.
What you want to do is to make an async call from a callback kind of function ('request'). For that, you must use Promise.
So your createObject function must return a new Promise object, or must be declared as an async function. In your case, since the request function use a callback pattern, you have to use a Promise object because it will provide you a callback that must be called when your Promise resolve.
function createObject() {
return new Promise((resolve, reject) => {
try {
console.log("Processing POST Loan.");
var data = {
"value1" : 1288,
"value2" : [{
"value3" : 3833,
"value4": [{
"value5": new Date()
}]
}]
}
var auth = Buffer.from("login:pass").toString('base64')
const res = request("http://url.com/resource", {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Authorization': "Basic "+auth
},
body: JSON.stringify(data)
}, function(error, response, body){
if(error) reject(error);
resolve({ response, body });
});
}
catch(err) {
reject(err);//the promise is rejected
}
});
}
async function uploadReport() {
console.log('cerate object')
const res = await createObject();
//in res you get 'response' and 'body' where you have the result of your API.
//it's up to you to adapt this to what you want
console.log(res);
}
uploadReport()

Jest tests of Promises

I need to write a Jest test for a function using Promises, which is working perfectly in the browser.
My code is as follows: a JQuery AJAX call is done, which returns a promise; when this promise is resolved, I call another function, which is also using another Promises internally!
So the code of my Jest test is globally like this:
function post(url) {
return $.ajax(url).then((result) => {
resolve(result);
});
}
function showAlert(message) {
return new Promise((resolve, reject) => {
require('anotherPackage').then(() => { // require returns a Promise
anotherPackage.showAlert(message); // will result in DOM update
}).then(resolve, reject);
});
}
function handleResponse(result) {
const promises = [];
...
promises.push(showAlert(message));
...
return Promise.all(promises);
}
test("Promises test", () => {
let response = null,
url = 'http://example.com/result.json';
return post(url).then([result, status, request] => {
response = parseResponse(result);
}).then(() => {
return handleResponse(response).then(() => {
return $('.alert').length;
}).then(value => {
expect(value).toBe(1); // doesn't work, value is 0 !!!
});
});
});
The handleResponse function is using AJAX request response, and in this use case, the function is calling another function which is using a promise internally which, when resolved, is creating an alert inside the DOM.
Actually, the test given as example doesn't work, because the expect call is done before the inner handleResponse promise is "completely resolved"!
So my question is: how can I handle this use case with Jest?
Best regards,
Thierry

My asynchronous javascript gets executed in the middle of the other function

Im trying to execute a function after the other one in Vue.js. I've already tried async/await, callback functions, .then, but it somehow doesnt want to load one after the other. What is a possible solution?
auth_mixin.js:
async auth () {
console.log("authban")
var token = this.getCookie("token")
var jsonData = {}
jsonData["token"] = token
console.log(jsonData)
var bodyFormData = new FormData();
bodyFormData.append('data', JSON.stringify(jsonData));
axios({
method: 'post',
url: 'backend/index.php?action=checkAuth',
data: bodyFormData,
headers: {'Content-Type': 'multipart/form-data'}
})
.then(function (response) {
console.log(response);
if(response.data.status==="OK"){
console.log("ok")
return true;
}else{
console.log("nem ok")
return false;
}
})
.catch(function (response) {
console.log(response);
return false;
});
}
Navbar.vue:
created () {
var result=false
this.auth().then(this.checkIfLoggedIn(result))
},
methods: {
checkIfLoggedIn (isLoggedIn) {
console.log("na ez lesz az erdekes "+isLoggedIn)
if(isLoggedIn === true){
console.log("true")
document.getElementById("logged_out").style.display="none";
document.getElementById("logged_in").style.display="block";
}else{
console.log("fail");
}
}
}
this.auth().then(this.checkIfLoggedIn(result))
You have two problems.
First: this.checkIfLoggedIn(result) calls checkIfLoggedIn immediately. You need to pass a function to then.
this.auth().then(() => this.checkIfLoggedIn(result))
Second: With that change, you call checkIfLoggedIn when auth resolves.
So when does auth resolve? Well, it is defined with the async keyword, so it resolves when it returns (unless it returns a promise, in which case it adopts that promise instead).
So what does it return? It has no return statement, so it returns undefined when it gets to the end … which is immediately after the call to axios (since you aren't awaiting that).
If you returned the return value of axios(...).etc then it wouldn't resolve until that promise resolved.
(Aside: You're using async, you should probably refactor to use await, try {} catch() {} instead of .then() and .catch()).

Javascript Promise: can't get the return value out of my function

I can not get the return object out of my function, so I can reuse it in the next function which should be called by my promise.then().
Struggling with researching, and can not find the solution.
engine.log is basically equivalent to console.log in my application.
firstRequest
.then( function (result) {
return secondRequest(result)
},
error => engine.log(error)
)
.then(
result => engine.log(result), // result is undefined, need the object here
)
let firstRequest = new Promise(function(resolve, reject) {
http.simpleRequest({
'method': 'GET',
'url': theUrl,
'dataType': 'json',
'timeout': 6000,
}, function (error, response) {
if (error) {
engine.log("Error: " + error)
reject()
}
if (response.statusCode != 200) {
engine.log("HTTP Error: " + response.status)
reject()
}
myObject = JSON.parse(response.data)
resolve(myObject)
})
});
function secondRequest(request) {
http.simpleRequest({
'method': 'GET',
'url': theSecondUrl,
'dataType': 'json',
'timeout': 6000,
}, function (error, response) {
if (error) {
engine.log("Error: " + error);
}
if (response.statusCode != 200) {
engine.log("HTTP Error: " + response.status);
}
myObject = JSON.parse(response.data)
return myObject // this is not being returned, to the .then()
})
}
Firstly, I see redundant code, so I would consolidate your requests into a single more generic function. It seems your desire not to do this is you feel you have to pass a 'request' parameter to your second function, but notice that you never use it. Just pass a url in both cases.
In your generic function, which below i'm calling 'makeRequest', be sure to return the promise, or else you'll get undefined. Also, be sure to call resolve on the parsed object, not return;
Also, I'd use catch for your errors, that way you'll catch it whenever an error occurs, whether it's in the first then or the second one.
This is untested, by the way, but I think the general points are in there.
makeRequest('somewhere')
.then(result => makeRequest('theSecondUrl'))
.then(result => engine.log(result))
.catch(error => engine.log(error));
function makeRequest (url, method, datatype, timeout) {
return new Promise(function(resolve, reject) {
http.simpleRequest({
'method': method || 'GET',
'url': url,
'dataType': datatype || 'json',
'timeout': timeout || 6000,
}, function (error, response) {
if (error) {
engine.log("Error: " + error)
reject()
}
if (response.statusCode != 200) {
engine.log("HTTP Error: " + response.status)
reject()
}
// use 'let' or else you've made a globally scoped variable
let parsed = JSON.parse(response.data)
resolve(parsed);
});
})
}
Javascript promises are asynchronous. Meaning they are resolved in the event loop, if the promise is resolved as soon as your call to .then() callback, then you will get the value in the next function as you expect.
Javascript promises are made asynchronous since typical API calls or database retrieving may involve network delays or any other delays to return the data. Thus the users will see these delays as performance issues. To avoid this kind of effect Javascript event loop is doing its job as running promises asynchronously. Meaning the code after promises is run most probably before it gets resolved or rejected. Therefore your next function will not have the value of first promise when its code is run.
In the next iteration or 1+ iterations of the event loop, your promise may get resolved/rejected and then may have populated with the corresponding value.
The solution =>
You should use async await to make any asynchronous code synchronous in javascript.
You could read more about it here.
https://javascript.info/async-await

Caller does not get correct value from async method with ajax

I am trying to wrap my ajax call around a promise. So when the ajax call is complete the promise gets resolved.
In the following snippet:
async function getDetails (data){
let promise = new Promise((resolve, reject) => {
$.ajax({
url: "/get-details",
type:"get",
contentType:"application/json",
data:{"text":data},
success: function(result){
resolve(result.response);
},
error: function(result){
reject(result.response);
}
});
});
let result = await promise;
console.log(`result: ${result}`);
return result;
}
function test() {
let result = getDetails("query-text");
console.log(`result in test : ${result}`);
}
test();
I get correct value in result of getDetails(). The result in test() is a promise object rather than the expected value.
How should I get the desired value from getDetails in an async manner using Promises?
That is because getDetails(...) returns a promise, not the parameter passed into the resolve() or reject() callbacks. Therefore, doing this should work:
async function test() {
let result = await getDetails("query-text");
console.log(`result in test : ${result}`);
}
Alternatively, you can do without an async method by simply waiting for the promise to resolve:
function test() {
let p = getDetails("query-text");
p.then(result => console.log(`result in test : ${result}`));
}
Even beter: using the new fetch() API
Since you're using ES6, you might want to consider using the fetch() API anyway:
async function getDetails (data){
let result = await fetch('/get-details').then(resp => resp.json());
console.log(`result: ${result.response}`);
return result.response;
}
async function test() {
let result = await getDetails("query-text");
console.log(`result in test : ${result}`);
}
test();
When you declare a function as async, it will implicitly return a Promise if not explicitly done so. This means your getDetails function will always return a Promise. Thus, you either need to use a .then callback to get your result from getDetails or use await to "unpack" the result from the promise.
Another thing that you can change in your code is that you do not need to wrap your ajax request in a promise:
jQuery API Documentation:
The jqXHR objects returned by $.ajax() as of jQuery 1.5 implement the
Promise interface, giving them all the properties, methods, and
behavior of a Promise...
This means that your ajax request will resolve and reject for you, and thus allows you to treat the return value like a Promise, allowing you to set your promise variable directly equal to your ajax request:
async function getDetails(data) {
let promise = $.ajax({
url: "/get-details",
type: "get",
contentType: "application/json",
data: {
"text": data
}
});
let result = await promise;
console.log(`result: ${result}`);
return result; // returns a new promise with the return value as the resolve
}
async function test() {
let result = await getDetails("query-text"); // await to "unpack" the promise returned via your async function "getDetails"
console.log(`result in test : ${result}`);
}
test();
But, as #Terry pointed out in his great answer, if you're just using jQuery for sending ajax request, there is really no need (unless you're really worried about browser support). fetch can do the job for you.

Categories

Resources