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()
Related
I am using WDIO and defining a customer reporter to integrate with the testrails api. The plan was to use axios to make these requests inside the testing hooks.
Unfortunately, I am unable to get axios to return any valid data on requests. In most cases when we await a response, the thread just stops executing entirely without any logging output. If I jimmy it enough sometimes I can get it to return an unresolved promise, but nothing I can do ultimately resolves the promise.
Also in none of my attempts have the requests been received by testrails (I've tested a few other urls as well, I'm fairly certain the issue is not at the destination).
I've made sure that network access and security are not factors. We have also attempted using both the axios post, and the straight up axios() methods, no luck there.
I'll copy the file below, I've added roughly a dozen attempts/configurations with notes on each as to what we're getting. The meat of the issue is in the addRun() method.
In most cases we never appear to resolve the promise. there is one exception, where we don't interact at all with the response, just log inside the then() statement. If we do that, we can see those logs, but the results of the axios call never take effect (the run is not created in testrails).
const WDIOReporter = require('#wdio/reporter').default
const axios = require('axios').default;
module.exports = class TestrailsReporter extends WDIOReporter{
constructor(options) {
/*
* make reporter to write to the output stream by default
*/
options = Object.assign(options, { stdout: true })
super(options)
}
// I have tried marking this as both async and not, no difference
async onRunnerEnd(suite) {
console.log("CHECKPOINT RUNNER END")
this.recordResults(caseIds[5], results[5], 'renters api tests', 5);
}
/**
* takes the results from a test suite and records them in testrails
* #param suiteId -- the suite defined in the testrails project
* #param projectId -- the project id defined in the testrails project
* #param caseIds -- a list of cases with which to create the test run
* #param results -- a list of case:result pairings
*/
async recordResults(caseIds, results, name, projectId) {
console.log(`CHECKPOINT RECORDING RESULTS ${projectId}`)
let testRun = await this.addRun(results['suiteId'], caseIds['cases'], name, projectId);
testRun.then(console.log)
await this.addResults(testRun, results['cases']);
}
async addRun(suiteId, caseIds, name = '', projectId) {
console.log("CHECKPOINT ADD RUN")
let addRunConfig = {
method: 'post',
url: `https://REDACTED.testrail.io/index.php?/api/v2/add_run/${projectId}`,
headers: {
'Content-Type': 'application/json',
Authorization: token,
Cookie: 'tr_session=041c4349-688f-440a-95a3-afc29d39320a'
},
data: JSON.stringify({
suite_id: suiteId,
include_all: false,
case_ids: caseIds,
name: name
})
};
// let x = axios.get('https://www.google.com/')
// console.log(x)
axios.defaults.timeout = 1000;
// THIS DOES NOT EXECUTE THE CODE INSIDE THE THEN STATEMENT, RETURNS PENDING PROMISE TO RESPONSE
// let response = axios(addRunConfig)
// .then(function (response) {
// console.log("WHAAAT?")
// return response.data.id;
// })
// .catch(function (error) {
// console.log("HELP!")
// console.log(error);
// });
// THIS DOES NOT EXECUTE THE CODE INSIDE THE THEN STATEMENT, NO LOGGING APPEARS AFTER
let response = await axios(addRunConfig)
.then(function (response) {
console.log("WHAAAT?")
return response.data.id;
})
.catch(function (error) {
console.log("HELP!")
console.log(error);
});
// THIS DOES NOT EXECUTE THE CODE INSIDE THE THEN STATEMENT
// await axios.post(`https://REDACTED.testrail.io/index.php?/api/v2/add_run/${projectId}`, addRunConfig)
// .then(
// function (response){
// console.log('WHAAAT?')
// console.log(response)
// console.log('NO WAY?')
// })
// THIS DOES NOT EXECUTE THE CODE INSIDE THE THEN STATEMENT, BUT RETURNS A PENDING PROMISE TO RESPONSE
// let response = axios.post(`https://REDACTED.testrail.io/index.php?/api/v2/add_run/${projectId}`, addRunConfig)
// .then(
// function (run){
// console.log('WHAAAT?')
// console.log(run)
// console.log('NO WAY?')
// })
// THIS DOES NOT EXECUTE THE CODE INSIDE THE THEN STATEMENT, BUT RETURNS A PENDING PROMISE TO RESPONSE
// let response = axios.post(`https://REDACTED.testrail.io/index.php?/api/v2/add_run/${projectId}`, addRunConfig)
// .then(
// function (run){
// console.log('WHAAAT?')
// })
// THIS DOES NOT EXECUTE THE CODE INSIDE THE THEN STATEMENT, BUT RETURNS A PENDING PROMISE TO RESPONSE
// let response = axios.post(`https://REDACTED.testrail.io/index.php?/api/v2/add_run/${projectId}`, addRunConfig)
// .then(run => {
// console.log('WHAAAT?')
// })
// THIS EXECUTES THE CONSOLE.LOG INSIDE THE THEN STATEMENT, BUT NOT AFTER
// let response = await axios.post(`https://REDACTED.testrail.io/index.php?/api/v2/add_run/${projectId}`, addRunConfig)
// .then(console.log('WHAAAT?'))
// THIS EXECUTES THE CONSOLE.LOG INSIDE THE THEN STATEMENT, AND AFTER
// let response = axios.post(`https://REDACTED.testrail.io/index.php?/api/v2/add_run/${projectId}`, addRunConfig)
// .then(console.log('WHAAAT?'))
// EXECUTES THE CONSOLE.LOG INSIDE THE THEN STATEMENT, NOTHING FROM THE CATCH, AND NOTHING AFTER
// const response = await axios(addRunConfig).then(console.log("HI")).catch(function (error) {
// console.log("HELP!")
// console.log(error);
// });
console.log("ANYTHING")
console.log(response)
return response
}```
Figured this out, it was the WDIOReporter parent class not playing nice with the asynchronous calls from axios. Solution found here:
https://github.com/webdriverio/webdriverio/issues/5701
Have you tried calling the method using
await axios.post(...) instead of defining everything in the addRunConfig object?
Not sure if it makes a difference, but it's something to try.
There is some confusion around the concepts of defining functions, calling functions and asynchronous functions here.
First of all: If you are calling an asynchronous function and do not want your calling function to return before that asynchronous function has returned, you want to await that call.
In this case, your recordResults function awaits something, and is thus async. Therefor, you probably want that onRunnerEnd awaits your call to recordResults. If you dont do that, the function will terminate prematurely and likely not wait for a result.
async onRunnerEnd(suite) {
console.log("CHECKPOINT RUNNER END")
await this.recordResults(caseIds[5], results[5], 'renters api tests', 5);
}
Secondly, if you use then and await together, the value returned by the await expression is whatever the function inside the then returns. Thus, all your attempts that have no return value inside the function called by then will never return anything but a void promise. There is no reason to combine these concepts.
Thirdly, putting a function call (rather than a declaration or reference) inside a then clause, will immediately call that function. I.e. .then(console.log('WHAAAT?')) just immediately calls console.log and also registers a non existent function as the callback for then (since console.log does not return a function reference).
Lastly, passing unbound functions is not going to work in general. Doing things like testRun.then(console.log) will not work, depending on the implementation of then and console.log. Either do testRun.then(console.log.bind(console) or testRun.then((x) => console.log(x)) to be on the safe side.
So, first of all, add that await inside onRunnerEnd, and then just use the await result without any then or catch in your addRun:
async addRun(suiteId, caseIds, name = '', projectId) {
console.log("CHECKPOINT ADD RUN")
let addRunConfig = {
method: 'post',
url: `https://REDACTED.testrail.io/index.php?/api/v2/add_run/${projectId}`,
headers: {
'Content-Type': 'application/json',
Authorization: token,
Cookie: 'tr_session=041c4349-688f-440a-95a3-afc29d39320a'
},
data: JSON.stringify({
suite_id: suiteId,
include_all: false,
case_ids: caseIds,
name: name
})
};
// let x = axios.get('https://www.google.com/')
// console.log(x)
axios.defaults.timeout = 1000;
let response = await axios(addRunConfig);
console.log(response);
console.log(response.data.id);
return response.data.id;
}
First of all, I'm aware this is not a good approach, need it as temporary solution for certain functions to return value, not promise. I know it's really not good permanent solution at all, but I need it for now.
What worries me, fetch sure finishes sooner - but it runs until the whiles times out, and then to console comes first the RETVAL false, and only then second line comes RETFETCH: {....} with returned json values - it seems the 'haveResponse' value does not change in the second 'then' - but can't see why, and how to bypass it.
It's a temporary workaround for old sync fns to read some data from remote service running on local pc on some port, but for now I can't rewrite the function which expects to receive data from this fn, so there must be no promise on the outside, need to wait for response and then return it.
function syncFetch(url) {
var haveResponse = false;
var reqtime = new Date();
try{
fetch(url, {
headers: {'Content-Type': 'application/json'},
method: 'POST',
timeout: 1500,
body: JSON.stringify({cmd:'init'})
})
.then(response => response.json())
.then(data => {
console.log('RETFETCH:', data);
haveResponse = data;
return data;
});
// timeout
while (haveResponse === false) {
var endDate = new Date();
if (haveResponse !== false) { return haveResponse; }
if ((endDate - reqtime)/1000 > 5) { // max 5 sec
return haveResponse;
}
}
return haveResponse;
} catch(e){
console.log('error', e);
haveResponse = -1;
}
return haveResponse;
}
console.log('RETVAL',syncFetch('http://127.0.0.1:3333/'));
Save yourself a few headaches, drop all the .then() and use the async/await syntax instead. No need for dirty timeout/while hacks.
I renamed syncFetch to asyncFetch, because your original code was never synchronous in the first place (and that's precisely why you are struggling so much, you believe it is when it's not). async/await don't make the code synchronous either. It's just awesome syntactic sugar around Promises.
(EDIT : you said in the comments that you can't add the async keyword to the parent function (asyncFetch), so here's a workaround to place it inside :
function asyncFetch(url) {
async function a() {
try {
const response = fetch(url, {
headers: { 'Content-Type': 'application/json' },
method: 'POST',
timeout: 1500,
body: JSON.stringify({ cmd: 'init' })
});
const data = await response.json();
return data; // This is a Promise<data>, not data directly, so it needs to be awaited
} catch (e) {
console.log('error', e);
return null
}
};
return a();
};
(async () => {
console.log('RETVAL', await asyncFetch('http://127.0.0.1:3333/')); // Needs to be awaited, and therefore also needs to be inside an async function
})();
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I use promise to handle async method getPropoalByPeriod but I can't get the obj data out of the foo function where console.log(89,foo(mycallback())) prints undefined. Please help to get data outside so that I can process them.
function foo(callback) {
var MyPromises = periods.map((p) => {
return new Promise((resolve, reject) => {
myFunctions.getPropoalByPeriod(p.id, localStart, function(err, rows) {
if (err) {
console.log(62, err);
return reject(err);
} else
var obj = {
id: p.id,
name: p.name,
savedServices: rows[0] ? rows[0].services : '',
};
resolve(obj)
})
})
})
Promise.all(MyPromises)
.then((p) => {
callback(p)
})
.catch((err) => console.log(79, err));
}
function mycallback(res) {
console.log(85, res)
return res
}
foo(mycallback)
console.log(89, foo(mycallback()))
This issue is different from what is on SO. It is using map() where I couldn't figure out how to apply await compared to the straightforward example of promise or async/await.
then accepts a function and send the result of the Promise to that function (the first argument of your callback function).
Promises are asynchronous by nature, and their output is not immediately available. That's why we use then method to get the result.
In your code, mycallback receives the result of all the Promises, and the value res (that is printed along with value 85) is what you get from the promises.
Remember, that because you used Promise.all on an array of promises, you get an array of results from all the succeeded promises. This means that res is an array, not a single value.
Your code suffers from some other problems. The first call to your foo method is good, and correctly prints the results. Your second call however (console.log(89,foo(mycallback()))) is incorrect, because instead of passing a function to foo, you are passing the result of calling callback(), which is undefined, to the foo function. Remove that line, and everything should be working.
Here is the complete revised code:
function foo() {
var MyPromises = periods.map((p) => {
return new Promise((resolve, reject) => {
myFunctions.getPropoalByPeriod(p.id, localStart, function(err, rows) {
if (err) {
console.log(62, err);
reject(err);
} else {
var obj = {
id: p.id,
name: p.name,
savedServices: rows[0] ? rows[0].services : '',
};
resolve(obj);
}
});
});
});
return Promise.all(MyPromises);
}
// Pure promises:
foo().then(r => console.log(r));
// or using async/await:
async function run() {
console.log(await foo());
}
run();
//
Remember, the point of using promises is freeing you from using callbacks. So there is no need to pass a callback to foo.
You can use .then or await to retrieve the value of a Promise. You can only use await in an async function; so you cannot use await in global score, and you have to define a function, and call it. So in your case, using .then is easier.
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
})
})
I'm using promises to query a rest api (using httpplease with the Promise plug-in):
api.call = function (myurl) {
return http.get({
"url" : myurl
});
}
This returns a promise that I can use with something like:
api.call (myurl)
.then (function (resp) {
// do whatever with the data
});
Now I'm trying to authenticate the api connection and for this I need to queue 2 async calls to the rest api:
1.- Ask for an authentication token
2.- Use the authentication token to make the actual call:
var getToken = function () {
var tokenUrl = "....";
return http.get({
"url": tokenUrl;
})
}
api.call = function (myurl) {
return getToken().then(function (token) {
return http.get({
"url" : myurl,
"headers" : {
"auth-token": token
}
})
})
}
The client code would remain the same:
api.call (myurl)
.then (function (resp) {
// do whatever with the data
});
Unfortunately, the code above is returning the final promise before the first one finishes (ie, token is undef in the final "then").
I'm probably missing something, but I thought that this should work after reading this:
"If you return a value, the next "then" is called with that value.
However, if you return something promise-like, the next "then" waits
on it, and is only called when that promise settles (succeeds/fails)"
Any idea how to do this?
M;
EDIT: Fixed typo in code