I can't understand Javascript asynchronous behavior.
I always thought that 'request' module would be synchronous. I used it , though, in my code, and something went absolutely wrong.
An example:
download_page = function(item) {
page = request.get( { url: 'http://test-fdfdfd.com/' + String(item) })
}
node = new App();
node.on('ready', () => {
console.log("Ready.");
Array.from(Array(3).keys()).forEach(item => download_page(item));
node.stop()
})
In that code, node should stop only after the three requests were completed. However, that didn't happen and I don't know why.
Could someone give me an explanation?
request is actually asynchronous.
You might want to return a Promise from your function, and then Promise.all all of them.
download_page = function(item) {
return new Promise((resolve, reject) => {
request.get( { url: 'http://test-fdfdfd.com/' + String(item) }, (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
node = new App();
node.on('ready', () => {
console.log("Ready.");
Promise.all(Array.from(Array(3).keys()).map(item => download_page(item)));
node.stop()
})
Related
I am trying to understand how promises work in JS by playing with swapi.dev. I would like to create a dynamic chain of promises (not using async/await) but it does not provide me with any result. In particular, the idea behind is to get all names of the given person (for instance Luke Skywalker) and dump them into the console.
Could anyone help me? What am I missing?
Thanks in advance.
"use strict";
const request = require("request-promise");
const BASE_URL = "http://swapi.dev/api";
var currentPromise = Promise.resolve();
callApiPromise(`${BASE_URL}/people/1`).then((data) => {
console.log("Getting vehicles' URLs");
const vehicles_URL = data["vehicles"];
console.log("Starting looping through URLs");
for (let i = 0; i < vehicles_URL.length; i++) {
console.log(`i=${i}, vehicle_URL=${vehicles_URL[i]}`);
currentPromise = currentPromise.then(function () {
console.log(".. getting vehicle name");
return getVehicleName[vehicles_URL[i]];
});
}
});
function getVehicleName(url) {
callApiPromise(url).then((vehicle_data) => {
var arrVehicleData = new Array();
arrVehicleData.push(vehicle_data);
console.log(arrVehicleData.map((vehicle) => vehicle.name));
});
}
function callApiPromise(url) {
return new Promise((resolve, reject) => {
callApi(url, (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
function callApi(url, callback) {
request
.get(url)
.then((response) => {
const json = JSON.parse(response);
callback(null, json);
})
.catch((err) => {
callback(err, null);
});
}
Some issues:
A missing return statement in getVehicleName
A syntax issue in getVehicleName[vehicles_URL[i]] (should be parentheses)
As the promises for getting the vehicle names are independent, you would not chain them, but use Promise.all
arrVehicleData will always only have one element. There is no reason for an array there where it is used.
You are also taking the wrong approach in using request.get. The bottom function turns that API from a Promise-API to a callback API, only to do the reverse (from callback to promise) in the function just above it. You should just skip the callback layer and stick to promises:
"use strict";
const request = require("request-promise");
const BASE_URL = "http://swapi.dev/api";
getJson(`${BASE_URL}/people/1`).then(data => {
return Promise.all(data.vehicles.map(getVehicleName));
}).then(vehicleNames => {
console.log(vehicleNames);
// Continue here...
});
function getVehicleName(url) {
return getJson(url).then(vehicle => vehicle.name);
}
function getJson(url, callback) {
return request.get(url).then(JSON.parse);
}
Finally, you should not use request-promise anymore since the request module, on which request-promise depends, has been deprecated
The getVehicleName doesn't return a promise. Instead it invokes a promise that by the time it will be resolved, the for loop invoking it will already be removed from the call stack.
This is a sample of promise chaining:
const promise = new Promise(resolve => resolve(1))
const promise1 = Promise.resolve(2)
const methodReturnPromise = () => new Promise(resolve => resolve(3))
promise.then(firstPromiseData => {
// do something with firstPromiseData
console.log(firstPromiseData)
return promise1
}).then(secondPromiseData => {
// do something with secondPromiseData
console.log(secondPromiseData)
return methodReturnPromise()
}).then(thirdPromiseData => {
// do something with thirdPromiseData
console.log(thirdPromiseData)
})
This question already has answers here:
How do I promisify native XHR?
(6 answers)
Closed 1 year ago.
I am on the newer side to programming; the concept of promises and async/await functionality is something I have had a hard time wrapping my head around. But I know that it is something I should be utilizing in this case.
Background: Building a prototype-banking application with Node, Express, XML2js npm package to parse the XML data that I am working with, XMLhttpRequest, and EJS for templating.
I have this get route in my server.js:
const parseString = require('xml2js').parseString;
app.get('/home', (req, res) => {
makeCall(`<root>
<slipped>
<mail>
<alike>-1845676614.3625278</alike>
<paid>uncle</paid>
<kill>something</kill>
<name>Stephen<name>
<men>such</men>
<firm>rubbed</firm>
<using>yesterday</using>
</mail>
</slipped>
<pour>-1247721160</pour>
<poet>language</poet>
<sets>-1907281866</sets>
<treated>proper</treated>
<judge>781679047</judge>
</root>`)
//Putting the format of the XML *response* above, to show what I am rendering from
setTimeout(function () {
res.render('home.ejs', {
name: dataList[0].root.slipped.mail.name
})
}, 1000);
})
I am wanting to have the app wait and finish makeCall(), before home.ejs gets rendered. If I don't wait, then it will propagate the old value instead. It does work how it is now, but I believe there is a much more efficient way of doing this.
How can I rewrite the above logic using promises or async/await behavior instead of setTimeout?
For reference, the makeCall():
const makeCall = (call) => {
myRequest.open('POST', 'http://111.222.3.444:55555')
myRequest.setRequestHeader('Content-Type', "application/x-www-form-urlencoded");
myRequest.send(call)
myRequest.onload = () => {
if (myRequest.status === 200) {
parseString(myRequest.responseText, (err, result) => {
dataList.unshift(result)
})
} else {
console.log('Something went wrong, status code: ' + myRequest.status)
}
}
}
Thank you in advance for any help you can provide me :)
Return a promise in makeCall so you can wait for it in your main method. Here is an example
const makeCall = (call) => {
return new Promise((resolve, reject) => {
myRequest.open('POST', 'http://111.222.3.444:55555')
myRequest.setRequestHeader('Content-Type', "application/x-www-form-urlencoded");
myRequest.send(call)
myRequest.onload = () => {
if (myRequest.status === 200) {
parseString(myRequest.responseText, (err, result) => {
dataList.unshift(result);
resolve();
})
} else {
console.log('Something went wrong, status code: ' + myRequest.status)
reject();
}
}
});
}
Then you can wait for it to finish to continue in your main method. You can do that by using promises like:
makeCal(...)
.then(() => your_success_logic)
.catch((e) => your_error_logic);
Or you can use async/await like this:
app.get('/home', async (req, res) => {
await makeCal(...);
res.render('home.ejs', {
name: dataList[0].root.slipped.mail.name
});
});
You can make something asynchronous by using the new Promise constructor. It takes a function with two callbacks (resolve,reject). If it fails, reject it. If it's successful you can use resolve. Node 10 and above you can use the async function like so and use the await keyword to wait until a asynchronous function resolves or rejects before moving foward.
const parseString = require('xml2js').parseString;
app.get('/home', async (req, res) => {
await makeCall(`<root>
<slipped>
<mail>
<alike>-1845676614.3625278</alike>
<paid>uncle</paid>
<kill>something</kill>
<name>Stephen<name>
<men>such</men>
<firm>rubbed</firm>
<using>yesterday</using>
</mail>
</slipped>
<pour>-1247721160</pour>
<poet>language</poet>
<sets>-1907281866</sets>
<treated>proper</treated>
<judge>781679047</judge>
</root>`)
//Putting the format of the XML *response* above, to show what I am rendering from
res.render('home.ejs', {
name: dataList[0].root.slipped.mail.name
})
})
const makeCall = (call) => {
return new Promise((resolve, reject) => {
myRequest.open('POST', 'http://111.222.3.444:55555')
myRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
myRequest.send(call)
myRequest.onload = () => {
if (myRequest.status === 200) {
parseString(myRequest.responseText, (err, result) => {
if (err) {
reject(err)
} else {
dataList.unshift(result)
resolve()
}
})
} else {
console.log('Something went wrong, status code: ' + myRequest.status)
reject(new Error(''Something went wrong, status code: ' + myRequest.status'))
}
}
})
}
Ho everyone
Got issue with setting promises and playing with results.
I have 2 arrays of URLs defined and objectives is to filter files (using fetch) by keeping only those that exists, then once completed trigger a function that shows the results.
I would like to use promise methodology and fetch() function.
I have create 1 promise per array that filters only existing files. I need to wait before the 2 promises are completed before triggering another function.
I looked at internet and tried multiple ways but cannot make it working as I expect. it shows the results but I cannot play with them so I may have done it wrongly.
Happy to get some helps !
See below my code :
const Primary = [
'https://jsonplaceholder.typicode.com/posts', //file exists
'/data/file01', //file dont exist
'https://jsonplaceholder.typicode.com/users', //file exists
'/data/file02' //file dont exist
];
const Diverse = [
'/data/file0.txt', //file dont exist
'https://jsonplaceholder.typicode.com/albums', //file exists
'/data/file5.txt' //file dont exist
];
const P1 = new Promise(function(resolve, reject) {
Primary.forEach(function (elem, index) {
fetch( elem )
.then(function(response) {
if (response.status != 404) {
arr1.push (response.url);
return (response.url);
}
})
.catch(function(error) {
console.log("Error ", error);
});
if (index==(Primary.length-1))
resolve (arr1);
});
});
const P2 = new Promise(function(resolve, reject) {
Diverse.forEach(function (elem, index) {
fetch( elem )
.then(function(response) {
if (response.status != 404) {
arr2.push (response.url);
return (response.url);
}
})
.catch(function(error) {
console.log("Error ", error);
});
if (index==(Diverse.length-1))
resolve (arr2);
});
});
// Promise.all waits until all jobs are resolved
Promise.all( [P1, P2] )
.then(function (responses) {
console.log ('Responses:', responses);
responses.forEach(function (element, index) {
console.log (element);
element.forEach(function (el, id) {
console.log (el);
});
});
console.log('Test sync');
});
Thanks again
Alex
This does not work properly as you wrote P1, P2 functions in a wrong way.
Diverse.forEach(function (elem, index) {
...
if (index==(Primary.length-1))
resolve(arr);
}
^This is really bad practice as JS is synchronous and does not return result that you want.
Please rewrite functions like this -
const P2 = new Promise((resolve, reject) => {
let fetchPromises = Diverse.map((elem, index) => fetch(elem));
Promise.all(fetchPromises)
then(responses => resolve(responses))
});
Happy coding!
You need a callback to get the result back to you, synchronous function will not return any result that you want.
const Primary = [
'https://jsonplaceholder.typicode.com/posts', //file exists
'/data/file01', //file dont exist
'https://jsonplaceholder.typicode.com/users', //file exists
'/data/file02' //file dont exist
];
const Diverse = [
'/data/file0.txt', //file dont exist
'https://jsonplaceholder.typicode.com/albums', //file exists
'/data/file5.txt' //file dont exist
];
const doFetch = (url) => fetch(url).then(response=>{
let promise = new Promise((resolve,reject) =>{
if(response.status == 200){
resolve(response.json())
}else{
reject(response.status)
}
})
return promise
})
let callback = {
success: (data) => {
console.log(data)
},
error:(err)=>{
console.log(err)
}
}
Primary.map( (url) => {
doFetch(url)
.then(callback.success, callback.error)
})
Diverse.map( (url) => {
doFetch(url)
.then(callback.success, callback.error)
})
You can use Promise.all()
Something like this should work
Promise.add(Primary.map(fetch)).then((allResponsens) => ...);
Promise.all will resolve once all of the promises provided as an argument resolve.
You can also use Promise.allSettled() if you don't care about catching network errors
I am learning Node. I have a console app that must make requests to web services in order. Specifically, I need to make three requests in order. In an attempt to make these requests, I'm using the built-in HTTPS module. I have one request successfully executing. But, I need to make three in succession. I'm not sure how to do this. Right now, I have:
console.log('Running Request #1...');
var options = {
host: 'example.com',
path: '/api/service',
port: 443,
method: 'GET',
headers: {
'api-key': '[Hidden]'
}
};
var req = https.request(options, (res) => {
res.on('data', (d) => {});
});
req.end();
req.on('error', (e) => {
console.error(e);
});
I'm not sure how to call my three requests in order. Yet, at the same time, gracefully handling an error. If I had promises, I would know how to chain them together and just use the catch handler. But, I'm not sure how to chain together requests since the HTTPS module uses the Arrow function syntax.
Any help is appreciated it.
Try this:
var https = require('https');
var urls = ['url1', 'url2', 'url3'];
var request = function(url) {
console.log(url);
return new Promise((resolve, reject) => {
https.get(url, (res) => {
res.on('end', () => {
resolve('what');
});
res.on('data', data =>{
});
}).on('error', e => {
reject(e);
});
});
};
var promise = request(urls.shift());
while(urls.length > 0) {
let url = urls.shift();
promise = promise.then(function() {
return request(url);
});
}
promise.catch(e => console.log);
consider use promise with reduce,something like this
var urls=['u1','u2','u3'];
var er=0
function getPromise(url) {
return new Promise(function (resolve,reject) {
setTimeout(function () {
console.log(url+ " is resolved in 2 sec")
er++
if(er==1)
{
reject(url)
}else{
resolve(url)
}
},2000)
})
}
urls.reduce(function (pre,cur) {
return pre.then(function () {
return getPromise(cur)
})
},new Promise(function (resolve,reject) {
resolve(null)
}))
.then(function (result) {
console.log("final result is "+result)
},function (e) {
console.log("something wrong happens : "+e)
})
Play with the code,I think it is want you want
I'm trying to extend some existing code with additional promises, but they are a new topic for me at the moment and i'm obviously missing something. This is running as part of a build scrip for npm.
All i am currently trying to make happen is for the final then to be called after the pack operation has happened for each architecture. I have tried wrapping it in a
return new Promise
But at the moment i am not returning anything from that function so i'm not sure what i should include in the resolve call at the end. If i just call the resolve with a true nothing happens, and wrapping it in a promise seems to cause the function to not actually run, and no errors are caught anywhere?
I'm guessing i am going about this completely wrong, all i want to achieve is to run another function once the previous one has completed?
Here's the code as it stands with the additional .then that i can't get to be called.
function build(cfg) {
return new Promise((resolve, reject) => {
webpack(cfg, (err, stats) => {
if (err) return reject(err);
resolve(stats);
});
});
}
function startPack() {
console.log('start pack...');
build(electronCfg)
.then(() => build(cfg))
.then(() => del('release'))
.then(paths => {
if (shouldBuildAll) {
// build for all platforms
const archs = ['ia32', 'x64'];
const platforms = ['linux', 'win32', 'darwin'];
platforms.forEach(plat => {
archs.forEach(arch => {
pack(plat, arch, log(plat, arch));
});
});
} else {
// build for current platform only
pack(os.platform(), os.arch(), log(os.platform(), os.arch()));
}
})
.then(() => {
console.log('then!');
})
.catch(err => {
console.error(err);
});
}
function pack(plat, arch, cb) {
// there is no darwin ia32 electron
if (plat === 'darwin' && arch === 'ia32') return;
const iconObj = {
icon: DEFAULT_OPTS.icon + (() => {
let extension = '.png';
if (plat === 'darwin') {
extension = '.icns';
} else if (plat === 'win32') {
extension = '.ico';
}
return extension;
})()
};
const opts = Object.assign({}, DEFAULT_OPTS, iconObj, {
platform: plat,
arch,
prune: true,
'app-version': pkg.version || DEFAULT_OPTS.version,
out: `release/${plat}-${arch}`,
'osx-sign': true
});
packager(opts, cb);
}
You didn't say what log is, but if it's a plain logging function, then it looks like you're passing in undefined (the result from calling log(...)) as the cb argument to pack. Perhaps you meant:
pack(plat, arch, () => log(plat, arch));
In any case, this won't do anything to wait for packing to finish. I don't know why you're not seeing any console output, but if you're looking for this output to happen after all the packing has finished, then you need to wrap packager in a promise. Something like:
var pack = (plat, arch) => new Promise(resolve => {
// ...
packager(opts, resolve);
});
And then use Promise.all instead of forEach to do all the packaging (in parallel if that's OK):
.then(paths => {
if (!shouldBuildAll) {
return pack(os.platform(), os.arch());
}
return Promise.all(['linux', 'win32', 'darwin'].map(plat =>
Promise.all(['ia32', 'x64'].map(arch => pack(plat, arch))));
})
.then(() => console.log('then!'))
.catch(err => console.error(err));