My function looks like this now:
var GetImages = async() => {
var images_array = [];
await request ({
url: `https://api.tumblr.com/v2/blog/nameblog/posts?api_key=${process.env.TUMBLR_KEY}&type=photo`,
json: true
}, (error, response, body) => {
if(error){
console.log('Unable to connect');
}else if(body.meta.status === "ZERO_RESULTS"){
console.log('Uable to find that address.');
}else if(body.meta.status === 200){
body.response.posts.forEach(function(obj) {
obj.photos.forEach(function(photo) {
if(photo.original_size.width>photo.original_size.height){
images_array.push(photo.original_size.url);
console.log("dawdaw");
}
});
});
//callback(images_array);
}
});
return images_array;
}
I have no idea, how return my array after i'll fill it with values. With callback it works fine, but i wanna do it with async/await methid in right way. Thank you for help.
create method to return promise for request and use that method with await
requestPromise = () => {
return new Promise(function(resolve, reject) {
request({
url: `https://api.tumblr.com/v2/blog/nameblog/posts?api_key=${process.env.TUMBLR_KEY}&type=photo`,
json: true
}, (error, response, body) => {
if (error) {
console.log('Unable to connect');
reject();
} else if (body.meta.status === "ZERO_RESULTS") {
console.log('Uable to find that address.');
reject();
} else if (body.meta.status === 200) {
body.response.posts.forEach(function(obj) {
obj.photos.forEach(function(photo) {
if (photo.original_size.width > photo.original_size.height) {
images_array.push(photo.original_size.url);
console.log("dawdaw");
}
});
});
resolve(images_array)
}
});
});
}
var GetImages = async() => {
try
{
images = await requestPromise();
return images;
}
catch(e){return [];}
}
Related
This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
app.post('/api/edit-profile', regularFunctions, async function (req, res) {
let email = req.body.email
let password_current = req.body.password_current
connection.query('SELECT * FROM accounts WHERE id = ?', req.body.id, async function (err, results) {
if (results.length > 0) {
bcrypt.compare(password_current, results[0].password, async function (err, isMatch) {
if (err) {
res.send('Unable to save settings')
res.end();
throw err
} else if (!isMatch) {
res.send('Password doesn\'t match.')
res.end();
} else {
let changed = []
// Password matches
if (req.body.password_new) {
let newPassword = req.body.password_new
let hashed_password = await hashPassword(newPassword)
connection.query('UPDATE accounts SET password = ? WHERE id = ?', [hashed_password, req.body.id], async function (error, results) {
if (results.affectedRows && results.affectedRows > 0) {
changed.push('password')
} else {
res.send('Unable to save settings')
res.end();
}
})
}
if (req.body.license_key) {
let newKey = req.body.license_key
axios.get(`https://voltcloud.net/api/hosting/check-key/${newKey}`, {
headers: {
authorization: 'Y1wUo3joP99JHiGM2orji0UYTey9gdqY'
}
}).then(function (response) {
let data = response.data
if (typeof data === 'object') {
if (data.active === 1) {
axios({
method: 'post',
url: `https://voltcloud.net/api/hosting/activate-key/${newKey}`,
headers: {
authorization: 'Y1wUo3joP99JHiGM2orji0UYTey9gdqY'
}
}).then(async function (response) {
if (response.data === 'Success') {
connection.query('UPDATE accounts SET license_key = ? WHERE id = ?', [newKey, req.body.id], async function (error, results) {
if (results.affectedRows && results.affectedRows > 0) {
changed.push('license key')
} else {
res.send('Unable to save settings')
res.end();
}
})
} else if (data === 'License already active!') {
res.send('License key is already active!')
res.end();
} else if (data === 'Failed to update key.') {
res.send('Unable to save settings')
res.end();
} else {
res.send('Unable to save settings')
res.end();
}
});
}
}
})
}
connection.query('UPDATE accounts SET email = ? WHERE id = ?', [email,req.body.id], async function (error, results) {
if (results.affectedRows && results.affectedRows > 0) {
changed.push('email')
} else {
res.send('Unable to save settings')
res.end();
}
});
let finalTxt = 'Successfully changed, '
if (changed.length > 1) {
changed.forEach(function (txt, index) {
if (index === 0) {
finalTxt = finalTxt + txt
} else if (index === 2) {
finalTxt = finalTxt + `and ${txt}.`
}
})
} else if (changed.length === 1) {
finalTxt = `Successfully changed ${changed[0]}.`
}
res.send(finalTxt)
res.end();
}
})
}
})
});}
I know this might seem like a very easy problem to some expert coders, but I am sort of new to this whole async and synchronous thing. Why is it that the "changed" array doesn't update even though it's being pushed to after the functions run? What I'm trying to do is have it only return one string that can be shown on the client-side but it doesn't seem to be changing it and only returning the "Successfully changed, "
This function confused me, as it has a lot of responsabilities as Mike 'Pomax' Kamermans pointed out, but I found the problem:
The connection.query method is non-blocking, meaning it will not wait for it's execution to end for it to advance to the next instructions.
When you are using async methods and Promise, it's nice to try and keep consistency over the methods (avoid mixing callback functions and async/await). I've refactored it over what it should look like if using async/await:
app.post('/api/edit-profile', regularFunctions, async function (req, res) {
let email = req.body.email
let password_current = req.body.password_current
let results = await executeQuery(connection, 'SELECT * FROM accounts WHERE id = ?', [req.body.id]);
if (results.length > 0) {
let isMatch = await comparePassword(password_current, results[0].password);
if (!isMatch) {
throw new Error(`Password doesn't match`);
}
let changed = []
// Password matches
if (req.body.password_new) {
let newPassword = req.body.password_new
let hashed_password = await hashPassword(newPassword)
let results = await executeQuery(connection, 'UPDATE accounts SET password = ? WHERE id = ?', [hashed_password, req.body.id]);
if (results.affectedRows && results.affectedRows > 0) {
changed.push('password')
} else {
throw new Error('Unable to save settings');
}
}
if (req.body.license_key) {
let newKey = req.body.license_key
let response = await axios.get(`https://voltcloud.net/api/hosting/check-key/${newKey}`, {
headers: {
authorization: '<redacted>'
}
});
let data = response.data
if (typeof data === 'object') {
if (data.active === 1) {
let response = await axios({
method: 'post',
url: `https://voltcloud.net/api/hosting/activate-key/${newKey}`,
headers: {
authorization: '<redacted>'
}
})
if (response.data === 'Success') {
let results = await executeQuery(connection, 'UPDATE accounts SET license_key = ? WHERE id = ?', [newKey, req.body.id]);
if (results.affectedRows && results.affectedRows > 0) {
changed.push('license key')
} else {
throw new Error('Unable to save settings');
}
} else if (data === 'License already active!') {
throw new Error('License key is already active!');
} else if (data === 'Failed to update key.') {
throw new Error('Unable to save settings');
} else {
throw new Error('Unable to save settings');
}
}
}
}
let results = await executeQuery(connection, 'UPDATE accounts SET email = ? WHERE id = ?', [email,req.body.id]);
if (results.affectedRows && results.affectedRows > 0) {
changed.push('email')
} else {
throw new Error('Unable to save settings');
}
let finalTxt = 'Successfully changed, '
if (changed.length > 1) {
changed.forEach(function (txt, index) {
if (index === 0) {
finalTxt = finalTxt + txt
} else if (index === 2) {
finalTxt = finalTxt + `and ${txt}.`
}
})
} else if (changed.length === 1) {
finalTxt = `Successfully changed ${changed[0]}.`
}
res.send(finalTxt)
res.end();
}
});
function executeQuery(conn, sql, params) {
return new Promise((resolve, reject) => {
conn.query(sql, params, function (err, data) {
if (err != null) {
return reject(err);
}
return resolve(data);
});
});
}
function comparePassword(val1, val2) {
return new Promise((resolve, reject) => {
bcrypt.compare(val1, val2, function (err, isMatch) {
if (err != null) {
return reject(err);
}
resolve(isMatch);
});
})
}
Notice that we're not using callback functions at all, and even where we don't have native Promise-based functions (i.e. mysql connection), we're delegating to a function that proxies the callback to deliver a Promise and keep consistency over the final implementation.
The original code isn't waiting for the two if branches to complete before sending the response. It's hard to structure code like this in callbacks, due to the nesting.
Try using async functions and await wherever possible. It allows for much more readable code and error handling is much easier. So this answer is more code review than a simple fix for your issue.
Split out some generic helper code that will be useful in other routes:
// Generate errors for the web, with context
function responseError(message, status, data){
const error = new Error(message)
error.status = status
for (const key in data){
error[key] = data[key]
}
return error
}
// Turn mysql callbacks into promises (or use util.promisify)
async function runQuery(query, values){
return new Promise((resolve, reject) => {
connection.query(query, values, function(error, results){
if (error) return reject(error)
return resolve(results)
})
})
}
async function runUpdateQuery(query, values){
const results = await runQuery(query, values)
if (!results) throw responseError('No update result', 500, { query })
if (!results.affectedRows) throw responseError('No affected rows', 400, { query })
return results
}
The code from the two if conditions can be easily separated, as well as the other account operations.
async function apiAuthUserId(id, password){
const results = await runQuery('SELECT * FROM accounts WHERE id = ?', id)
if (!results.length) throw responseError('No account', 400, { id })
const isMatch = await bcrypt.compare(password_current, results[0].password)
if (!isMatch) throw responseError('Password doesn\'t match', 400)
return true
}
async function apiUpdatePassword(id, password){
let newPassword = req.body.password_new
let hashed_password = await hashPassword(newPassword)
await runUpdateQuery('UPDATE accounts SET password = ? WHERE id = ?', [hashed_password, req.body.id])
return id
}
async function apiUpdateEmail(id, email){
await runUpdateQuery('UPDATE accounts SET email = ? WHERE id = ?', [email, id])
return email
}
async function apiUpdateLicenseKey(id, licenseKey){
const response_license = await axios.get(`https://voltcloud.net/api/hosting/check-key/${licenseKey}`, {
headers: {
authorization: 'somekey'
}
})
const data = response_license.data
if (!data) {
throw responseError('No license key response data', 500, { response: response_license })
}
if (data.active !== 1) {
throw responseError('License key not active', 400, { key: licenseKey })
}
const response_activate = await axios({
method: 'post',
url: `https://voltcloud.net/api/hosting/activate-key/${licenseKey}`,
headers: {
authorization: 'somekey'
}
})
switch (response_activate.data){
case 'License already active!':
throw responseError('License key is already active!', 400, { response: response_activate })
case 'Failed to update key.':
throw responseError('Unable to save settings!', 400, { response: response_activate })
case 'Success':
await runUpdateQuery('UPDATE accounts SET license_key = ? WHERE id = ?', [licenseKey, req.body.id])
return licenseKey
default:
throw responseError('Unable to save settings!', 500, { response: response_activate })
}
}
Then your route code can be a bit cleaner and show what needs to be done, rather than how to do it all.
app.post('/api/edit-profile', regularFunctions, async function (req, res) {
const changed = []
try {
const { id, email, password_current } = req.body
await apiAuthUserId(id, password_current)
// Password matches
if (req.body.password_new) {
await apiUpdatePassword(id, req.body.password_new)
changed.push('password')
}
// License key
if (req.body.license_key) {
await apiUpdateLicenseKey(id, req.body.license_key)
changed.push('license key')
}
await apiUpdateEmail(id, email)
changed.push('email')
let finalTxt = `Successfully changed ${changed.join(' and ')}.`
res.send(finalTxt)
}
catch (error) {
// If your not using transactions, might need to deal with partial `changed` responses here.
console.error('Error /api/edit-profile', error)
res.status(error.status||500).send(`Error: ${error.message}`)
}
});
This question already has answers here:
Chained promises not passing on rejection
(4 answers)
Closed 3 years ago.
I want to return a fetch promise to upper layer to use, but I found even this fetch promise fail (be catched), upper layer's then still be called. Is it possible "upper layer's then" only be called when fetch success?
export default function request(url, options) {
.......
return fetch(url, options)
.then(checkStatus)
.then(parseJSON)
.then(data => {
// debugPrint("receive response" + JSON.stringify(data));
if (+data.status.code !== 200) {
message.error(data.status.message || "please retry");
}
return data;
})
.catch(err => {
message.error(err.toString() || "please retry");
return err;
});
}
// then I call request this way:
export function userLogin(account) {
return request(`${domain}/signin/get_accesstoken`, {
method: "POST"
}).then(data => {
// even catch be called, this then still be called. Is it possible only call this then when catch not be called ?
do something;
return data;
});
}
Second edit:
I try to return a promise in then, but look like it is not a promise be returned, I don't know why.
export default function request(url, options) {
.......
return fetch(url, options)
.then(checkStatus)
.then(parseJSON)
.then(data => {
// debugPrint("receive response" + JSON.stringify(data));
if (+data.status.code !== 200) {
message.error(data.status.message || "please retry");
}
return new Promise(resolve=>{
resolve(data)
});
})
.catch(err => {
message.error(err.toString() || "please retry");
return new Promise((resolve, reject)=>{
reject(err)
});
});
}
Edit third:
export default function request(url, options) {
.......
return fetch(url, options)
.then(checkStatus)
.then(parseJSON)
.then(data => {
// debugPrint("receive response" + JSON.stringify(data));
if (+data.status.code !== 200) {
message.error(data.status.message || "please retry");
}
return data;
})
.catch(err => {
message.error(err.toString() || "please retry");
return;
});
}
// then I call request this way:
export function userLogin(account) {
return request(`${domain}/signin/get_accesstoken`, {
method: "POST"
}).then(data => {
// add this
if (!data) {
return
}
do something;
return data;
});
}
if you want to call your upper layer's then only in case of success then throw some error in catch block of fetch instead of returning err.
export default function request(url, options) {
.......
return fetch(url, options)
.then(checkStatus)
.then(parseJSON)
.then(data => {
// debugPrint("receive response" + JSON.stringify(data));
if (+data.status.code !== 200) {
message.error(data.status.message || "please retry");
}
return data;
})
.catch(err => {
message.error(err.toString() || "please retry");
throw new Error('fetch failed'); // throw error
});
}
I have this service
service.getCurrentUser = function () {
var def = $q.defer();
if (service.user == null)
{
$http.get("./api/GetCurrentUser/")
.success(function(data) {
service.user=data;
def.resolve(data);
})
.error(function() {
def.reject("Failed to get user");
});
}
else
def.resolve(service.user);
return def.promise;
}
in my controller I want to call this and wait for return then if the user is in a certain group run other code
How do I write it so it uses the promise returned by the service
The promise implementation can be like:
service.getCurrentUser = function () {
return new Promise((resolve,reject)=>{
if (service.user == null) {
$http.get("./api/GetCurrentUser/")
.success(function (data) {
service.user = data;
resolve(data);
})
.error(function () {
reject("Failed to get user");
});
}else{
resolve(service.user);
}
});
}
You can call it as:
service.getCurrentUser()
.then(user => {
console.log('user', user);
})
.catch(error => {
console.log('error', error);
});
I want to set timeout in my fetch method, and I follow this github issue.
from #mislav answer, we could custom timeout method like below.
// Rough implementation. Untested.
function timeout(ms, promise) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error("timeout"))
}, ms)
promise.then(resolve, reject)
})
}
timeout(1000, fetch('/hello')).then(function(response) {
// process response
}).catch(function(error) {
// might be a timeout error
})
and improved by #nodkz
function timeoutPromise(ms, promise) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error("promise timeout"))
}, ms);
promise.then(
(res) => {
clearTimeout(timeoutId);
resolve(res);
},
(err) => {
clearTimeout(timeoutId);
reject(err);
}
);
})
}
but I want to call that method in ES7 async/await syntax, I try below way but failed.
async request() {
try {
let res = await timeout(1000, fetch('/hello'));
} catch(error) {
// might be a timeout error
}
}
and how could I use it in ES7 async/await syntax?
thanks for your time.
update
thanks for #Bergi reply, and I display my code in http request step.
ES7 fetch
'use strict';
import { configApi, } from 'tyrantdb-config';
import { timeoutPromise, } from 'xd-utils-kit';
const keyResponse = configApi.keyResponse;
const KEY_DATA = keyResponse.data;
module.exports = {
async fetchLogin(url, params, dataRely) {
let {
self, processor, route,
storage, isLocalStoraged,
email, password, _warning, } = dataRely;
self.setState({ isWait: true, });
try {
let res = await timeoutPromise(10, fetch(url, params));
if (res.status >= 200 && res.status < 300) {
let resJson = await res.json();
let resData = resJson[KEY_DATA];
let cache = {
...resData,
password,
};
if (isLocalStoraged !== true) {
processor(cache, storage);
}
global.g_user = cache;
self.setState({ isWait: false, });
// clean user info which already bind to self[this]
self.email = self.password = null;
self.isLocalStoraged = false;
route();
} else {
console.log(`[login] response code: ${res.status}`);
self.setState({ isWait: false, });
_warning();
}
} catch(error) {
console.error(error);
}
},
saveLoginState: (cache, storage) => {
storage.save({
key: 'loginState',
rawData: cache,
});
},
openURL: (url, Linking) => {
Linking.canOpenURL(url).then(supported => {
if (!supported) {
console.log(`can\'t handle url: ${url}`);
} else {
return Linking.openURL(url);
}
}).catch((err) => {
console.log(`error occurred: ${err}`);
})
},
};
ES6 fetch
'use strict';
import { configApi, } from 'tyrantdb-config';
import { timeoutPromise, } from 'xd-utils-kit';
const keyResponse = configApi.keyResponse;
const KEY_DATA = keyResponse.data;
module.exports = {
fetchLogin(url, params, dataRely) {
let {
self, processor, route,
storage, isLocalStoraged,
email, password, _warning, } = dataRely;
self.setState({ isWait: true, });
timeoutPromise(1000, fetch(url, params))
.then((res) => {
if (res.status >= 200 && res.status < 300) {
return res.json();
} else {
console.log(`[login] response code: ${res.status}`);
self.setState({ isWait: false, });
_warning();
}
})
.then((resJson) => {
let resData = resJson[KEY_DATA];
let cache = {
...resData,
password,
};
if (isLocalStoraged !== true) {
processor(cache, storage);
}
global.g_user = cache;
self.setState({ isWait: false, });
// clean user info which already bind to self[this]
self.email = self.password = null;
self.isLocalStoraged = false;
route();
})
.catch(error => console.error(error))
.done();
},
saveLoginState: (cache, storage) => {
storage.save({
key: 'loginState',
rawData: cache,
});
},
openURL: (url, Linking) => {
Linking.canOpenURL(url).then(supported => {
if (!supported) {
console.log(`can\'t handle url: ${url}`);
} else {
return Linking.openURL(url);
}
}).catch((err) => {
console.log(`error occurred: ${err}`);
})
},
};
above ES6 fetch will catch output below, I think #Bergi is right, It's my code fault, not timeoutPromise error.
2016-06-08 22:50:59.789 [info][tid:com.facebook.React.JavaScript] [login] response code: 400
2016-06-08 22:50:59.808 [error][tid:com.facebook.React.JavaScript] { [TypeError: undefined is not an object (evaluating 'KEY_DATA')]
line: 103795,
column: 29,
sourceURL: 'http://172.26.129.189:8081/index.ios.bundle?platform=ios&dev=true' }
2016-06-08 22:50:59.810 [error][tid:com.facebook.React.JavaScript] One of the sources for assign has an enumerable key on the prototype chain. This is an edge case that we do not support. This error is a performance optimization and not spec compliant.
2016-06-08 22:50:59.810 [info][tid:com.facebook.React.JavaScript] 'Failed to print error: ', 'One of the sources for assign has an enumerable key on the prototype chain. This is an edge case that we do not support. This error is a performance optimization and not spec compliant.'
I'm trying to convert my existing code using BlueBird, please suggest a best option to chain multiple request. Error happening in each callback needs to be redirected to rendered with different error.
request(option1, function (error, response, body) {
if (!error && response.statusCode == 200) {
var data= JSON.parse(body);
if(data.valid){
if(data.expired){
next();
} else {
request(option2, function (error2, response2, body2) {
var data2= JSON.parse(body2);
if(data2.valid) {
request(option3, function (error3, response3, body3) {
next();
})
} else {
res.json({error:'Error1'});
}
})
}
} else {
res.json({error:'Error2'});
}
} else {
res.json({error:'Error3'});
}
})
This is pretty straightforward, also note your current code doesn't handle errors in the second and third requests and this does:
var request = require("request-promise"); // request - converted to bluebird
request(option1).then(data=> {
if(!data.valid) throw Error("Error3");
if(data.expired) return;
return request(option2).then(JSON.parse);
}).then(data2 => {
if(!data2) return; // didn't need to fetch additional data
if(!data2.valid) throw Error("Error2");
return request(option3);
}).then(() => {
next();
}, e => {
res.json(error: e.message);
// better log this.
});
var rp = require('request-promise');
function handleError(err) {
res.json({
error: err.message
});
}
function parse(data) {
if (data) {
return JSON.parse(data);
}
}
rp(option1)
.then(parse)
.then(function (data) {
if (!data || !data.valid) {
throw Error('Error2');
}
if (data.expired) {
return;
}
return option2;
})
.then(rp)
.then(parse)
.then(function (data2) {
if (!data2 || !data2.valid) {
throw Error('Error1');
}
return option3;
})
.then(rp)
.then(parse)
.then(function () {
next();
})
.catch(handleError);
You don't need to manually check for statusCode but if you need to do so, first you have to add resolveWithFullResponse attribute to your option1 object, which allows you to receive the response object:
function checkStatusCode(response) {
if (response.statusCode !== 200) {
throw Error('Error3');
}
return response.body;
}
// add resolveWithFullResponse attribute to option1
option1.resolveWithFullResponse = true;
rp(option1)
.then(checkStatusCode)
.then(parse)
//...