In an effort to remove the use of jQuery from my code, I tried to replace the $.Deferred(); by new Promise().
I noticed the usage are slightly different, and I'm still learning how it works.
Here is a simplified extract from my code:
function do_match (resolve, reject) {
fetch( /* ... */).then (async function(response) {
/* do some suff */
document.getElementById("match").insertAdjacentHTML('beforeend', '<div class="player"></div>');
resolve("done");
});
}
function do_myMarket () {
var elements = document.querySelectorAll('.player');
//here elements is sometimes null...
}
p1 = new Promise(do_match);
p1.then(do_myMarket, null);
While I would have expect do_myMarket to only be called after the promise is resolved, if the fetch is not fast enough, do_myMarket can be called before the elements are available in the page.
Putting breakpoints if elements is null and resolve() confirmed me this behavior.
Am I missing something? Why would this happen?
After some readings from #VLAZ and more testing, I found out it's because of the async in the unnamed function.
The promise p1 was resolved by the return value of the fetch function, which would not wait for completion because of the async keyword, thus making resolve("done"); useless.
And I tried, same behavior with or without the call to resolve.
This comes from, what I think now, as a wacky example from MDN:
// Function to do an Ajax call
const doAjax = async () => {
const response = await fetch('Ajax.php'); // Generate the Response object
if (response.ok) {
const jVal = await response.json(); // Get JSON value from the response body
return Promise.resolve(jVal);
}
else
return Promise.reject('*** PHP file not found');
}
}
// Call the function and output value or error message to console
doAjax().then(console.log).catch(console.log);
The above is all antipattern if I understood correctly.
The correct way is the page dedicated to the .json() method:
function doAjax() {
fetch(/* ... */)
.then(response => response.json())
.then(data => {
//...
})
.catch(console.error);
}
Related
I'm making a request to a service that generates several strings in rapid succession. My problem arise from having to return a promise with this service, as I do not control the service process that returns the string.
I want to emphasize the fact that I necessarily need to return a promise.
Right now, what I have is the main function (handler.ts) and it doesn't mantain any business logic, including only the following:
public static callback: any;
public static async init(configuration, callback): Promise<any> {
this.callback = callback;
...
return new Promise((resolve, reject) => {
try {
const result = await Service.bootstrap(configuration)
return resolve(result)
} catch(err) {
reject(err)
}
}
}
This handler calls the service, which has the bootstrap function that performs a call to a .js file that obtains some information from my computer and then returns a string about it.
import loadComputerInformation from 'load.js'
public async bootstrap() : Promise<any> {
loadComputerInformation();
function useComputerInfoString() {
// window.getInfo is generated by loadComputerInformation(), and I can not control
// when does it return it, as it generates several strings with rapid succession
// and until the function exists, I will not get my string.
if (typeof window.getInfo !== 'function') {return;}
const data = window.getInfo();
if (data.finished) {
clearTimeout(timeoutId);
const infoString = data.computerInfo;
Service.axiosCall(infoString);
}
}
// I have to set an interval to perform the call several times, and until it resolves it
// it will not stop performing this call.
const timeoutId = setInterval(useComputerInfoString, 500);
}
return;
}
Therefore, the problem that I'm facing is that my promise gets lost in another thread, and I can not return the value from Service.axiosCall(infoString), which is just a standard axios call, but that necessarily needs the infoString.
Adding the axios call function just in case it is useful. Right now, I'm using the callback passed to the handler.js to return the axios call, but I want to be able to include it in the Promise without the necessity of a callback function
public static async axiosCall(blackbox): Promise<any> {
await axios.post('https://somecall.com/info', blackbox)
.then((response) => { this.callback(element)
return element);
}
}
Any idea of how to solve this?
Highlight
Please note that loadComputerInformation() asynchronously loads Window.getInfo(), but it does not resolve only one value, but several, therefore I can not await on Window.getInfo() because at the beggining it does not exist, and will only return undefined
Also, right now, the code is up and running, but the way it is returning the value is with the callback and not as a promise.
Try a bootstrap function whose returned promise resolves with the response returned from an axios call.
This suggestion (based on information in the post) is vanilla JavaScript to demonstrate how to the problem might be approached. Obviously modification to integrate it into the application will be needed:
const bootstrap = () => new Promise( resolve => {
loadComputerInformation();
let state = "load";
const timer = setInterval( ()=> {
if( state == "load") {
if( typeof window.getData != "function") {
return;
}
state = "get";
}
let data;
if( state == "get") {
data = window.getData();
if(!data.finished) {
return;
}
// state = "request";
}
clearInterval(timer);
resolve( axios('post', "https:example.com/info", data.computerInfo) );
}, 100); // 1/10th second?
};
Note this answer has been modified to use a state machine to wait for getData to be loaded asynchronously, then to wait for a call to getData to return a data object with a finished property, before resolving the promise returned with the promise returned by the axios call - the first answer was simpler right up until it needed to wait for getData code to be loaded asynchronously.
Beneath the question lies a problem that may have to be written off to code debt: JavaScript is event driven. loadComputerInformation appears to have been written for its results to be polled. It is hard to imagine a justifiable reason why it was left in that state.
Update: Added a simpler demonstration jsfiddle, https://jsfiddle.net/47sfj3Lv/3/.
reproducing the problem in much less code I'm trying to move away from jQuery.
Some of my code, for populating some tables, has code like this
var hb = new hbLister(url: '#attributes.listURL#')
.getData(#url.page#, #url.per#)
.search(searchColumn, searchParam)
.render();
hbLister would initialize some things
getData would perform an $.ajax call
search wouldconsole.log('filtering data') and apply the search conditions against a javascript object
render would put the results on the page.
Importantly, search wouldn't fire until after the ajax call in getData finished.
So, now I have this ajax constructor. I've abbreviated as much of this code as I can.
let ajax = function (options, hooks, headers) {
let that = this;
// enforce parameter types
// copy parameters to this.base
this.base = { options: options, hooks: hooks, headers: headers }
return function (url, options, data, hooks, headers) {
// enforce variable types
// merge options and hooks with their base.counterparts
headers = new Headers(Object.assign({}, that.base.headers, headers));
options.headers = headers;
return fetch(url, options)
.then(response => {
return response.json().then(json => {
console.log('processing');
if (response.ok) {
// it's omitted here but the Base functions are defined
// in the constructor parameters
hooks.successBase(json, response.status, response.headers);
hooks.success(response.json, response.status, response.headers)
} else {
hooks.failureBase(json, response.status, response.headers);
hooks.failure(response.json, response.status, response.headers)
}
})
});
}
}
The idea is that I can say
let tableLister = new ajax()
And thengetData can call
tableLister = tableLister(hb.url, // url
{ type: "GET" }, // options
config.data, // data
{ success: successFn } // hooks, success being the callback
)
The jQuery call would properly give me and then processing and then filtering data.
This function gives me filtering data, an error, and thenprocessing, because I cannot seem to get the chain(.search(...).render()) to wait til the ajax call is done.
Here's a self-contained example on jsFiddle, https://jsfiddle.net/47sfj3Lv/3/
I am sure the answer is in async and await, but I have not been able to get it to work.
Here is an example of what I've tried
return await (async function () {
console.log('fetching')
let fetcher = await fetch(url, options);
console.log('getting json');
return await fetcher.json().then((json) => {
console.log('have json, doing hooks');
if (fetcher.ok) {
let argXHR = { json: json}
hooks.successBase(argXHR, hooks.params);
hooks.success.forEach(v => v(argXHR, hooks.params));
hooks.afterSend(argXHR, hooks.params);
} else {
let argXHR = { json: json,}
hooks.failureBase(argXHR, hooks.params);
hooks.failure.forEach(v => v(argXHR, hooks.params));
hooks.afterError(argXHR, hooks.params);
}
console.log('finished hooks')
})
}())
And no matter what I do, the chain, continues before this await finishes..
I got code with XMLHttpRequest to work. The method chain (.getData().search(...).render()) works with this, because this doesn't allow the ajax function to return before the request is finished and callbacks are executed. **I'd still prefer to make .fetch() work.
let xhr = new XMLHttpRequest()
let urlParams = [];
Object.keys(data).forEach((v) => urlParams.push(v + '=' + encodeURIComponent(data[v])));
urlParams = urlParams.join('&')
xhr.open(options.method, options.url, false);
xhr.onreadystatechange = function(state) {
if (this.readyState == 4) {
let json = JSON.parse(xhr.responseText)
hooks.successBase(json, xhr.status, xhr.getResponseHeader);
hooks.success.forEach(v => v(json, xhr.status, xhr.getResponseHeader));
}
}
xhr.onerror = function() {
let json = JSON.parse(xhr.responseText)
hooks.failureBase(json, xhr.status, xhr.getResponseHeader);
hooks.failure.forEach(v => v(json, xhr.status, xhr.getResponseHeader));
}
for (h in headers) {
xhr.setRequestHeader(h, headers[h])
}
xhr.send(urlParams)
This was difficult for me to understand, so I wanted to share if anyone else has the same issue.
It seems that an async method will break a method chain, there's no way around that. And since fetch is asynchronous, await must be used, and in order for await to be used, the calling method must be declared async. Thus the method chain will be broken.
The way the method chain is called must be changed.
In my OP, I linked https://jsfiddle.net/47sfj3Lv/3/ as a much simpler version of the same problem. StackOverflow's 'fiddle' effectively blocks 'fetch' for security reasons, so I need to use JSFiddle for demonstration.
Here's a working version of the same code using then and how/why it works, and a slightly shorter version, because await can be specified with the the fetch, obviously.
let obj = {};
// methods with fetch ideally should be specified async.
// async calls will automatically return a promise
obj.start = async () => {
console.log('start begins')
let retText = "",
fetcher = fetch('/', {}).then(response => response.text())
.then(text => {
console.log('fetch\'s last then')
retText = text
})
// fetcher has just been declared. It hasn't done anything yet.
console.log('fetch requested, returned length:', retText.length)
// this makes the fetcher and sequential then's happen
await fetcher;
console.log('await let fetch finish, returned length:', retText.length)
// Async functions will return a promise, but the return specified here
// will be passed to the next 'then'
return obj
}
obj.middle = () => {
console.log('middle called')
// Because this is not declared async, it behaves normally
return obj;
}
obj.end = () => {
console.log('end called')
// Because this is not declared async, it behaves normally
}
console.log('Method 1: ', obj.start()
// Because start is Async, it returns obj wrapped in a promise.
// As with any function, step may be named Anything you like
.then((step) => step.middle())
// Middle is not Async, but since it's wrapped in then
// it returns a promise
.then((step) => step.end())
)
// This is just wrapped in a timer so the two logs don't intermix with each other
// This is definitely the preferred method. Non-async chain-methods that return
// a reference to their object, do not need to wrapped in then().
setTimeout(function() {
console.log('------------------------')
console.log('Method 2: ', obj.start()
// Because start is Async, it returns obj wrapped in a promise.
// As with any function, step may be named Anything you like
.then((step) => step.middle().end())
)
}, 3000)
I'm creating website using Johnny-five,React and node.js to control my Arduino board but I got stuck on handling async/await function. So, user is sending chosen port (COM1) for example to server, server then creates new instance of board
async function checkPortConnection(port) {
let board = new five.Board({port: port});
let success;
await board.on('error', () => {
success = false;
});
await board.on('ready', () => {
success = true;
});
return success;
}
I've thought that keyword await will stop function execution and wait for board response which takes about 7 seconds, but when I do this:
checkPortConnection(port).then((data)=>{
console.log(data);
});
I'm getting 'undefined', (because I'm getting success which is undefined?)
And after that server will send response if chosen port is correct or not.
But my question is, how to get proper response from checkPortConnection() function?
I think the issue is that you are listening for events, but this in and of itself isn't a Promise. Also, if they would be and you would use await you would never reach the code to register the ready event. The following should fix that issue:
async function checkPortConnection(port) {
return new Promise((resolve, reject) => {
let board = new five.Board({port: port});
board.on('error', error => resolve( false ));
board.on('ready', event => resolve( true ));
});
}
Personally I would also do the following, as the Promise will either use then or catch later, so you might want to ignore the boolean bit altogether:
board.on('error', reject);
board.on('ready', resolve);
I have to make an async call to a 3rd party API and I want the code execution to wait for the response.
async function getOrderData(orderId) {
return new Promise(function (resolve, reject) {
var service = new APIservice();
service.GetOrderData(oid, function (event) {
if (event && event.hasErrors() == false) {
resolve(event.result);
} else {
reject(null);
}
});
});
}
var order = getOrderData(orderId);
//from here the code should only resume once the var order is defined with whatever's returned from the api
This is the top level code (not async) so I cannot use await.
EDIT:
Answering some suggestions:
this.$onInit = function () {
this.order = "wrong value";
getOrderData(orderId).then(order => {
this.order = "correct value";
});
};
this function will end with "test" being "wrong value". This is what I am trying to avoid.
You can await the Promise returned by getOrderData().
However, in order to use await you need to wrap your call to getOrderData in an async function (there is no such thing as top-level await at the moment - it may one day be a thing however. At present, it can only be used when inside an async function):
// getOrderData function initializaion...
(async _ => {
var order = await getOrderData(orderId); // wait to recieve data before proceeding
// You can now write code wih `order`
})().catch(err => {
console.error(err);
});
Alternatively, you can write your code in a .then callback (which await is simply just syntactic sugar for) from the returned Promise of getOrderData() like so:
// getOrderData function initializaion...
getOrderData(orderId).then(order => {
// write code in here which relies on `order`
}).catch(err => {
console.error(err);
});;
you need something like this:
async function the_whole_thing() {
async function the_api_wrapper() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('joe!'), 10);
});
}
let result = await the_api_wrapper();
console.log(result);
}
the_whole_thing();
Note #1: I just swapped out the API you are calling with setTimeout, as it is also a function that takes a callback (similar to your case) and it also ensures stuff happen asynchronously.
Note #2: note that the async/await syntax can only be used in the body of another async function, so thats why I wrapped the whole thing, well, in the_whole_thing.
I'm adding some functions to an Angular App and here is the thing: I'm trying to use a function that creates a promise to get data from the server, but every time I try to use it, it returns undefined. I've "debugged" with console.log printing my variable with the result of my function as value and it prints Promise{'pending'}
Here is the function of the promise and the variable I'm trying to assign.
all_allies(text = ''){
return new Promise((resolve, reject) => {
const _text = text ? /${text} : ''
const path = `${this.env.apiPath}/all_allies${_text}`
this.$http
.get(path)
.then(response => {
const { data } = response
resolve(data)
return data;
})
.catch(error => reject(error))
})
Variable
let allies = this.AliadosFactory.all_allies();
As you can see the function and the variable are in different scripts.
I've tried using await reserved word but still doesn't work
Can you try this method?
let allies = await this.AliadosFactory.all_allies();
console.log(allies);
or like this?
this.AliadosFactory.all_allies().then(allies => console.log(allies);
I sure it should work,
Hope this helps.
Have nice day :)
That's because when you perform assignment the Promise is not resolved / rejected yet.
There are two simple solutions:
1. Using then()
this.AliadosFactory.all_allies().then(result => console.log(result));
2. Using async/await
(note that you need an async method in your class)
async foo() {
let allies = await this.AliadosFactory.all_allies();
console.log(allies);
}
Also in all_allies() you don't need to return the value after calling the resolve() method;