I'm chaining 3 ajax requests with to a RESTful endpoint:
PUT some data (return {} and OK)
GET the data I just put
show data
I've set up a chain of promises using .then(). But the request does not happen in the expected order (1,2,3) but rather (2,1) and starting with a OPTIONSrequest.
Why are they not happening in the expected order?
How can I ensure the correct sequential order?
var _id = x;
function doReqs() {
putData(_id, data)
.then(getData(_id))
.then(showData);
}
// returns empty object {}
function putData(id, data) {
return $.ajax({
method: 'PUT',
url: http://xxx,
contentType: 'application/json'
});
}
// returns JSON {"data": {"xx": "xx}}
function getData(id) {
return $.ajax({
method: 'GET',
url: http://xxx
});
}
function showData(data) {
console.log(data);
}
In this code:
function doReqs() {
putData(_id, data)
.then(getData(_id))
.then(showData);
}
The .then(getData(_id)) part is just wrong. It's wrong for two reasons.
.then() is supposed to be passed a function reference. When you pass getData(_id), you are executing that function immediately and passing the return value from that function (which is a jqXHR object) to .then(). That's not what you're supposed to pass to .then().
Because you're executing getData(_id) IMMEDIATELY, it will not execute properly in the promise chain sequence.
Remember, any time you pass a func() with the parens after it as an argument, it executes that function immediately and passes it's return value as the argument. That is NOT what you want with .then() for the above reasons.
If you're trying to control what is being passed to getData(), then you can either make sure the right thing is returned from putData() because that's what will be passed to getData() or you can make a stub function that will pass the right thing:
function doReqs() {
putData(_id, data)
.then(function() {
return getData(_id);
})
.then(showData);
}
Or, you could do it this way:
function doReqs() {
putData(_id, data)
.then(getData.bind(null, _id))
.then(showData);
}
Or, since the resolved value of putData() is what will be passed as an argument to the next step in the promise chain (which is getData), you could do this:
function putData(id, data) {
return $.ajax({
method: 'PUT',
url: http://xxx,
contentType: 'application/json'
}).then(function() {
// make sure putData passes the id to the next step in the chain
return id;
});
}
function doReqs(id) {
putData(id, data)
.then(getData)
.then(showData);
}
Here's a working example of chaining in action:
function delay(t, val) {
return new Promise(function(resolve) {
setTimeout(resolve.bind(null, val), t);
});
}
function first(arg) {
console.log("running first..., arg = ", arg);
return delay(500, 10);
}
function second(arg) {
console.log("running second..., arg = ", arg);
return delay(100, 100);
}
function third(arg) {
console.log("running third..., arg = ", arg);
}
first(1).then(second).then(third);
Related
Is there a way I can write this block of code without all the repeated code for calling axios?
export const handler = async (event, useAwait) => {
const { path, data } = JSON.parse(event);
if (useAwait) {
return await axios(`https://example.com/api${path}`, {
method: 'post',
headers: {
'api-key': key,
},
data: data,
});
} else {
// non-blocking
axios(`https://example.com/api${path}`, {
method: 'post',
headers: {
'api-key': key,
},
data: data,
});
return true;
}
};
Put the promise into a variable, and then you can conditionally return it.
export const handler = async (event, useAwait) => {
const { data } = JSON.parse(event);
const prom = axios(`https://example.com/api${url}`, {
method: 'post',
headers: {
'api-key': key,
},
data: data,
});
return useAwait ? (await prom) : true;
};
That said, you may as well return the Promise itself - awaiting something that you're returning immediately doesn't help since you're not immediately inside a try.
return useAwait ? prom : true;
But calling this function without returning the result looks like a bad idea because then you may get an unhandled rejection. You may wish to add a .catch to the Promise in that case to avoid that.
export const handler = (event) => {
const { path, data } = JSON.parse(event);
const prom = axios(`https://example.com/api${path}`, {
method: 'post',
headers: {
'api-key': key,
},
data: data,
});
prom.catch(()=>{}); // prevent uncaught rejection
return prom;
}
If you don't test the return value of handler to be strictly equal to true, calling code can rely on promise objects being truthy in a conditional expression as needed, and the logic can be simplified to an ordinary function that returns a promise without using await or taking a useAwait parameter.
The dummy catch handler added to prom is to prevent generating an uncaught-rejected-promise error if the caller doesn't handle rejection of the axios promise, and can be omitted if uncaught rejection errors are acceptable. How calling code is meant to operate without waiting for the axios call completed is unclear and not covered here.
One of the reasons for suggesting major simplifications is the // non-blocking comment in the posted code. Async functions always return a promise object when called with no blocking effect, either to continued execution of calling code or browser operation.
The only effective difference between using await or not (in the post) is the value used to resolve the returned promise by handler.
My friend and I have been struggling with Node.js callbacks since yesterday. We have the following function:
// helperFunction.js
function foo(param) {
request.get({
url: <url>
headers: {<headers>}
}, (err, response, data) => {
array = []
obj.forEach(function (entry) {
// do stuff with array
};
});
return array;
});
}
module.exports.foobar = foo;
then we call that from our app.js.
Since yesterday, we have updated the code to wait for the callback by using a function, like so:
// app.js
//var bar = require('./helperFunction');
//console.log(helperFunction.foobar('param')); // prints undefined
function bar(){
console.log('Log something')
}
foo(bar);
but we don't know how to pass the parameter to foo. I tried to add param (which is a string) to bar but it doesn't work.
For the record, I'm aware of other posts such as this, but I cannot make it work on my code.
In foo you just add a callback parameter and instead of returning you call this function. As a convention, the first parameter of the callback function is the error object. If no error occurred, this object is null and the following parameters are the actual result of the function. Your code didn't include error handling so I added it. If error exists you won't receive any data and foo can't calculate whatever it tries to calculate. In this case, foo should either try to solve the problem itself or propagate the error to its caller.
function foo(param, cb) {
request.get({
url: <url>
headers: {<headers>}
}, (err, response, data) => {
if (err) {
return cb(err);
}
array = []
obj.forEach(function (entry) {
// do stuff with array
};
});
cb(null, array);
});
}
function bar(err, data){
console.log('Log something')
}
foo('some param', bar);
Pass a function to foo. Something like:
foo(() => bar("Hi, I'm something"));
function foo(fn, err) {
if (!err && fn instanceof Function) {
fn();
}
}
function bar(someThing){
console.log(`Log ${someThing}`);
}
I am looping through a map, where I want to make a separate AJAX call with each map value as parameter, to fetch some data and log it. See below. This is working, but I'd like to have the AJAX calls go in order of the map. Because each call is asynchronous, so seems like I should use promises to achieve execution in order. But I am new to promises and don't really know how to do it here. I have look elsewhere on here but could not find anything. Please help.
map.forEach(function(url, key) {
log(url);
});
function log(url) {
$.ajax({
url: url,
dataType: 'json',
success: function (result) {
console.log(result.value);
console.log(result.name);
console.log(result.action);
}
});
}
Since $.ajax returns a promise, you can use promise chaining to achieve what you want
var p = $.when();
map.forEach(function(url, key) {
p = p.then(function() {
return log(url);
});
});
function log(url) {
return $.ajax({
url: url,
dataType: 'json',
success: function (result) {
console.log(result.value);
console.log(result.name);
console.log(result.action);
}
});
}
Note: the code above uses only jQuery, no native promises
Or using reduce function of Array
map.reduce(function(p, url) {
return p.then(function() {
return log(url);
});
}, $.when());
If you can use ES2015+, so have native Promises,
map.reduce((p, url) => p.then(() => log(url)), Promise.resolve());
If you wanted, you can also do it like this
function log(url) {
return $.ajax({
url: url,
dataType: 'json'
});
}
map.reduce((p, url) => p.then(results => log(url).then(result => results.concat(result))), Promise.resolve([]))
.then(results => {
results.forEach(result => {
console.log(result.value);
console.log(result.name);
console.log(result.action);
})
});
The difference being as that all the console.log's would happen once the LAST request finished (and if any fail, none of the console log's would happen)
If you could use async/await syntax in your project, then nothing could be easier:
async function log(url) {
return await
$.ajax({
url: url,
dataType: 'json',
})
.then(function(result) {
console.log(result.value);
console.log(result.name);
console.log(result.action);
});
}
async function run() {
for (var i = 0; i < map.length; i++) {
await log(map[i]);
}
}
run();
You see, I changed forEach to for loop. It's important for await usage, because it provides (instead of forEach and other callback based loops) synchronicity of async log calls.
UPD Here is the Plunker which demonstrates such an approach.
To chain promises should work:
function ajaxPromises(urls) {
return Promise.all(urls.map(function(url) {
return $.ajax({
url: url,
dataType: 'json'
})
}))
}
Usage:
ajaxPromises(['http://yoururl.com','http://yoururl.com'])
.then(function (result) {
// do something with result
})
.catch(function (error) {
// do something with error
})
I have 2 functions:
the first one make an http post to get an xml string
function post(url, formData) {
return new Promise(function (resolve, reject) {
// make an http post and get results (xml string)
request(url, formData, function(error, xml) {
if (error) reject(error)
resolve(xml)
})
})
}
the second transform that xml to an object
function xmlToObject(xml) {
return new Promise( function (resolve, reject) {
// transform xml string to an object using xml2js for example
xml2js(xml, function(error, obj) {
if (error) reject(error)
resolve(obj)
})
})
}
Now I want to call post request and get an xml string and then transform it to an object, so which one is correct and why:
post(url, formData).then( function (xml) {
xmlToObject(xml).then( function (obj) {
// do some work
})
})
Or
post(url, formData).then( function (xml) {
xmlToObject(xml).then( function (obj) {
return obj
})
}).then( function (obj) {
// do some work
})
Or
post(url, formData).then( function (xml) {
return xmlToObject(xml)
}).then( function (obj) {
// do some work
})
Or
post(url, formData).then( xmlToObject )
.then( function (obj) {
// do some work
})
promise.then(function(obj) {
return obj
})
is pointless and should be replaced by just promise (omitting the then call).
….then(function(xml) {
xmlToObject(xml)
// ^ note the missing `return`
}).then(function(obj) {
…
});
is the only one in your collection that does not work. If you don't return anything, nothing can be awaited (and no result be obtained), and the next callback in the chain will be invoked immediately with undefined.
post(url, formData).then(function(xml) {
xmlToObject(xml).then(function(obj) {
// do some work
})
})
That does work but is cumbersome. You'll get the indentation pyramid of doom, and by neglecting to return anything from the then callbacks you make it impossible to chain anything to the outer promise. This is especially important for handling errors at the end of the chain.
The correct way of chaining promises is therefore
post(url, formData).then(function(xml) {
return xmlToObject(xml);
}).then(function (obj) {
// do some work
})
which is exactly equivalent to the shorter
post(url, formData).then(xmlToObject).then(function(obj) {
// do some work
});
If you want to do some work using both xml and obj, nesting (with return!) is an option out of many.
You can construct your promise chain w/ those above functions in the following way:
post(url, formData)
.then(xmlToObject)
.then(doSomeWork);
function doSomeWork(obj) {
//do some work
}
this one is correct
post(url, formData).then( function (xml) {
return xmlToObject(xml)
}).then( function (obj) {
// do some work
});
Reason:
when return a promise in promise resolver,You can just continue add .then handler.Maybe promise is designed to use this feature to avoid callback hell
i prefer for clear chaining this variant:
return Promise.resolve()
.then(function () {
return post(url, formData);
})
.then(function (xml) {
return xmlToObject(xml);
})
.then(function (object) {
return something;
});
When using JQuery.Deferred is it OK to invoke reject() directly? Without having invoked a async function?
Perhaps I want some kind of test in the beginning of my async function. If the test fails I want to reject immediately. See the first if block below.
function doSomethingAsync() {
//Test if the ajax call should be invoked
var testFailed = true;
var dfd = $.Deferred();
//Check if test failed
if (testFailed) {
var asyncResult = {
success: false,
data: 'test failed'
};
//Is this OK usage of reject on the same thread?
dfd.reject(asyncResult);
return dfd.promise();
}
$.get('/api/testapi/get').done(function (data) {
var asyncResult = {
success: true,
data: data
};
dfd.resolve(asyncResult);
}).fail(function (err) {
var asyncResult = {
success: false,
data: err
};
dfd.reject(asyncResult);
});
return dfd.promise();
}
When using JQuery.Deferred is it OK to invoke reject() directly? Without having invoked a async function?
Yes, it's totally OK to return an already rejected promise, and to reject deferreds immediately. You only might need to verify that your callbacks don't rely on asynchronous resolution, which jQuery does not guarantee (in contrast to A+ implementations).
Notice that in your code you should use then instead of manually resolving the deferred:
function doSomethingAsync() {
var testFailed = /* Test if the ajax call should be invoked */;
var dfd = testFailed
? $.Deferred().reject('test failed')
: $.get('/api/testapi/get');
return dfd.then(function (data) {
return {
success: true,
data: data
};
}, function (err) {
return {
success: false,
data: err
};
});
}
You can do it quickly, as your function return a Promise object:
return Promise.reject('test failed');