JavaScript problem with promise chaining example - ecmascript-6

I am having a hard time understanding chaining promises in javaScript so I decided to make an example and practice some code this is what I want to do...
Do a "heavy task" for 5 seconds
Do a "medium task" for 3 seconds after heavy task finishes
successfully
If "medium task" is successful to a "small" task of 2 second
The "small task" must display the success message of the heavy task
If "medium task" fails then do an "error task" of 1 second stating
failure reason
while all these are going on do "Some other tasks..."
var p = new Promise(function(resolve, request) {
setTimeout(function() {
console.log("Inside heavy task...");
resolve("Heavy task was a success");
}, 5000);
})
.then(function(value) {
setTimeout(function(value) {
console.log("Inside medium task...");
resolve(value);
//reject("Medium task failed !");
}, 3000);
})
.then(function(value) {
console.log("Inside small task...");
console.log("From small task : " + value);
})
.catch( function(reson){
setTimeout(function(reason){
console.log("Inside error task...");
console.log("Failed due to "+reason);
},1000);
});
console.log("Some other tasks...");
I understand that my code is wrong can someone correct this and explain how this should be done.

Firstly, If you want to pass additional parameters to the callback function of setTimeout then it is done as follows
setTimeout(callback, time, param1, param2, ...)
Now in your code snippet, wrap the second setTimeout function in a promise and return that promise. Also if you want the small task to be asynchronous then wrap it inside a promise also and return that promise.
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("Inside heavy task...");
resolve("Heavy task was a success");
}, 5000);
})
.then(function(value) {
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("Inside medium task...");
resolve(value);
// reject("Medium task failed !");
}, 3000);
});
return p2;
})
.then(function(value) {
console.log("Inside small task...");
console.log("From small task : " + value);
})
.catch(function(reason) {
setTimeout(function(){
console.log("Inside error task...");
console.log("Failed due to "+ reason);
}, 1000);
});
console.log("Some other tasks...");
I hope this is helpful :)

Related

How can I make the commit wait until the if statement has ran?

The function makes a transaction using mysql2 and the promise wrapper.
For each task, there can be multiple links, formatted in JSON.
For each link, I need to insert into the DB, after the task has been inserted. However, if the JSON is wrong (see "no" not "name" below), an error needs to be thrown and the commit should never happen.
{
"title": "Test new from POSTMAN many link",
"header": "POSTMAN test many links",
"category": "POSTMAN test many links",
"notes": "Task created from POSTMAN many link",
"links": [{"name": "Lenovo", "link": "https://www.lenovo.com"}, {"no": "Microsoft", "link": "https://www.microsoft.com"}],
"level": 2
}
I have tried the nested try/catch block but this does not run before the commit below it
await conn.beginTransaction();
const [task] = await conn.query("INSERT INTO tasks SET ? ", [taskObj]);
if (req.body.links.length > 0) {
req.body.links.map(async e => {
try {
const link = {
task_id: task.insertId,
name: e.name,
link: e.link
};
await conn.query("INSERT INTO links SET ? ", [link]);
} catch (err) {
console.log(err);
conn.rollback();
conn.release();
}
});
}
// this runs before my if statement above
console.log("about to commit");
await conn.commit();
conn.release();
return res.send({ data: taskObj, message: "Task created" });
} catch (e) {
conn.rollback();
conn.release();
console.log(e);
return res.send({ message: "error" });
}
I want the error to ensure the commit doesn't happen if either the task insert fails or a link insert fails.
See example:
(async () => {
try {
await conn.beginTransaction();
const [task] = await conn.query("INSERT INTO tasks SET ? ", [taskObj]);
if (req.body.links.length > 0) {
const promises = req.body.links.map(async e => {
const link = {
task_id: task.insertId,
name: e.name,
link: e.link
};
await conn.query("INSERT INTO links SET ? ", [link]);
});
await Promise.all(promises);
}
// this runs before my if statement above
console.log("about to commit");
await conn.commit();
conn.release();
return res.send({ data: taskObj, message: "Task created" });
} catch (e) {
await conn.rollback();
await conn.release();
console.log(e);
return res.send({ message: "error" });
}
})();

Integrate mysql in waterfall async

I need to save to db inside an async waterfall series.
I've tried to integrate these two function after the clean function
function connectDb(next) {
pool.getConnection(function(err, connection) {
if (err) console.log(err);
conn = connection;
}, next);
},
function saveDb(next) {
let sql = "UPDATE media SET media_url = ? WHERE media_url = ?";
conn.query(sql, [dstKey, srcKey], function (error, results, fields) {
if (error) {
conn.release();
console.log(error);
}else{
console.log("media db updated");
}
}, next)
}
The problem is that these two functions block the code execution. How can I integrate it in the function below? I've tried to wrap the function in promise but it is also not working.
async.waterfall([
function download(next) {
s3.getObject({
//param
},
next);
},
function transform(response, next) {
resizeMedia(response.Body ).then( ( file ) => { next();} ).catch( (err) => { reject(err) } ); }
},
function upload(next) {
var fileData = fs.createReadStream('/tmp/'+dstKey);
if (isVideo ) { var ContentType = 'video/mp4' }
if (isAudio ) { var ContentType = 'audio/mp3' }
s3.putObject({
//param
},
next);
},
function clean(next) {
// Stream the transformed image to a different S3 bucket.
fs.unlinkSync('/tmp/'+dstKey);
s3.deleteObject({
//param
},
next);
}
], function (err) {
if (err) {
console.error('Error');
callback(null, "Error");
return;
} else {
console.log('Success');
callback(null, "Done");
return;
}
callback(null, "Done");
return;
}
);
The purpose of async waterflow is to block the waterfall until the callback is called.
P.S. Usually you should not create a new db connection each time. The connection should be done once when the application start and get used whenever you need.
I highly recommend you to use knex.js instead, it return promises by default and if you like to use it inside async waterfall (and wait for resolve) you can call .asCallback.
I've found the problem, if someone fall into the same issue here my solution:
If a waterfall function has a response, this response is automatically added as first argument in the next function. In my code the mistake was simple (after night's sleep), the s3.deleteObject and s3.putObject has response, this response need to be setted as first argument and the callback as last, as you say I've used only the callback as argument (next) and this broke my code.
[...]
function upload(next) {
s3.putObject({
//param
},
next);
},
function clean(response, next) { // response in arguments
s3.deleteObject({
//param
},
next);
}
[...]

d3 - Can't return data from json request?

I'm trying to use d3.json() inside of a function to return data Spotify's API given an artist ID (such as 5K4W6rqBFWDnAN6FQUkS6x), but I can't figure out how to effectively return the data. The function looks like
// Get artist's related artist's information
function relatedArtists(id){
var jsonPromise = new Promise(function(resolve, reject) {
// Async JSON request
d3.json('https://api.spotify.com/v1/artists/' + id + '/related-artists', function(error, data){
if(error) reject(error);
resolve(data.artists);
});
});
jsonPromise.then(function(success) {
console.log(success);
//return(success) //doesn't work
});
jsonPromise.catch(function(error){
console.error(error);
});
}
I've tried creating a variable within the function and then modifying it
function relatedArtists(id){
var testVar = 'hello';
var jsonPromise = new Promise(...{
// Async JSON request
d3.json(...)
});
jsonPromise.then(function(success) {
testVar = success;
});
return(testVar);
}
But testVar remains 'hello', despite my best efforts. I've done some reading about scope and promises, but am happy to do more if there's some core mechanic of either that I'm not understanding. Thanks for reading!
The response will never be available in your calling code due to asynchronous nature of requests. You can use Promises (as supposed by Alexander T. and you, good choice in many cases!) but d3.queue does a good job, too. In my snippet you can see how to run code with the results of multiple requests.
function buildRelatedArtistUri(id) {
return 'https://api.spotify.com/v1/artists/' + id + '/related-artists';
}
d3.queue()
.defer(d3.json, buildRelatedArtistUri('5K4W6rqBFWDnAN6FQUkS6x'))
.await(function(error, data) {
// data and data.artists are available in this function‘s scope only
console.log(data.artists);
});
d3.queue()
.defer(d3.json, buildRelatedArtistUri('5K4W6rqBFWDnAN6FQUkS6x'))
.defer(d3.json, buildRelatedArtistUri('3nFkdlSjzX9mRTtwJOzDYB'))
.await(function(error, data1, data2) {
// this function will be called once both functions have finished
console.log(data1.artists, data2.artists);
});
<script src="https://d3js.org/d3.v4.min.js"></script>
You can return Promise and use relatedArtists function like so
function relatedArtists(id) {
return new Promise(function(resolve, reject) {
d3.json('https://api.spotify.com/v1/artists/' + id + '/related-artists', function(error, data) {
if (error) {
reject(error);
} else {
resolve(data.artists);
}
});
});
}
relatedArtists('5K4W6rqBFWDnAN6FQUkS6x')
.then(function (data) {
console.log(data);
})
.catch(function (error) {
console.log(error);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
In this case, you can not assign the value to testVar, because d3.json is the asynchronous method and that means that d3.json can be done after code execution.

javascript promise catch confusion [duplicate]

What is the best way to handle this scenario. I am in a controlled environment and I don't want to crash.
var Promise = require('bluebird');
function getPromise(){
return new Promise(function(done, reject){
setTimeout(function(){
throw new Error("AJAJAJA");
}, 500);
});
}
var p = getPromise();
p.then(function(){
console.log("Yay");
}).error(function(e){
console.log("Rejected",e);
}).catch(Error, function(e){
console.log("Error",e);
}).catch(function(e){
console.log("Unknown", e);
});
When throwing from within the setTimeout we will always get:
$ node bluebird.js
c:\blp\rplus\bbcode\scratchboard\bluebird.js:6
throw new Error("AJAJAJA");
^
Error: AJAJAJA
at null._onTimeout (c:\blp\rplus\bbcode\scratchboard\bluebird.js:6:23)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
If the throw occurs before the setTimeout then bluebirds catch will pick it up:
var Promise = require('bluebird');
function getPromise(){
return new Promise(function(done, reject){
throw new Error("Oh no!");
setTimeout(function(){
console.log("hihihihi")
}, 500);
});
}
var p = getPromise();
p.then(function(){
console.log("Yay");
}).error(function(e){
console.log("Rejected",e);
}).catch(Error, function(e){
console.log("Error",e);
}).catch(function(e){
console.log("Unknown", e);
});
Results in:
$ node bluebird.js
Error [Error: Oh no!]
Which is great - but how would one handle a rogue async callback of this nature in node or the browser.
Promises are not domains, they will not catch exceptions from asynchronous callbacks. You just can't do that.
Promises do however catch exceptions that are thrown from within a then / catch / Promise constructor callback. So use
function getPromise(){
return new Promise(function(done, reject){
setTimeout(done, 500);
}).then(function() {
console.log("hihihihi");
throw new Error("Oh no!");
});
}
(or just Promise.delay) to get the desired behaviour. Never throw in custom (non-promise) async callbacks, always reject the surrounding promise. Use try-catch if it really needs to be.
After dealing with the same scenario and needs you are describing, i've discovered zone.js , an amazing javascript library , used in multiple frameworks (Angular is one of them), that allows us to handle those scenarios in a very elegant way.
A Zone is an execution context that persists across async tasks. You can think of it as thread-local storage for JavaScript VMs
Using your example code :
import 'zone.js'
function getPromise(){
return new Promise(function(done, reject){
setTimeout(function(){
throw new Error("AJAJAJA");
}, 500);
});
}
Zone.current
.fork({
name: 'your-zone-name',
onHandleError: function(parent, current, target, error) {
// handle the error
console.log(error.message) // --> 'AJAJAJA'
// and return false to prevent it to be re-thrown
return false
}
})
.runGuarded(async () => {
await getPromise()
})
Thank #Bergi. Now i know promise does not catch error in async callback. Here is my 3 examples i have tested.
Note: After call reject, function will continue running.
Example 1: reject, then throw error in promise constructor callback
Example 2: reject, then throw error in setTimeout async callback
Example 3: reject, then return in setTimeout async callback to avoid crashing
// Caught
// only error 1 is sent
// error 2 is reached but not send reject again
new Promise((resolve, reject) => {
reject("error 1"); // Send reject
console.log("Continue"); // Print
throw new Error("error 2"); // Nothing happen
})
.then(() => {})
.catch(err => {
console.log("Error", err);
});
// Uncaught
// error due to throw new Error() in setTimeout async callback
// solution: return after reject
new Promise((resolve, reject) => {
setTimeout(() => {
reject("error 1"); // Send reject
console.log("Continue"); // Print
throw new Error("error 2"); // Did run and cause Uncaught error
}, 0);
})
.then(data => {})
.catch(err => {
console.log("Error", err);
});
// Caught
// Only error 1 is sent
// error 2 cannot be reached but can cause potential uncaught error if err = null
new Promise((resolve, reject) => {
setTimeout(() => {
const err = "error 1";
if (err) {
reject(err); // Send reject
console.log("Continue"); // Did print
return;
}
throw new Error("error 2"); // Potential Uncaught error if err = null
}, 0);
})
.then(data => {})
.catch(err => {
console.log("Error", err);
});

Using Lodash to loop through https.get when parsing XML to JSON in Node

I'm trying to parse XML to JSON in Node. I'm using xml2js. I'd like to incorporate Lodash to loop through each number in an array and use the corresponding url to convert the XML to JSON. When I use the code below, I get a Non-whitespace before first tag error. Any idea what I'm doing wrong?
const no = [78787878,78787879, 787878780];
_.forEach(no, https.get('https://tsdrapi.uspto.gov/ts/cd/casestatus/'+no+'/info.xml', function (res) {
res.on('data', function (chunk) {
response_data += chunk;
});
res.on('end', function () {
parser.parseString(response_data, function (err, result) {
if (err) {
console.log('Got error: ' + err.message);
} else {
console.log(util.inspect(result, false, null));
}
});
});
res.on('error', function (err) {
console.log('Got error: ' + err.message);
});
}));
Honestly the forEach helper in LoDash is kind of silly. forEach is a prototype method of any Array instance. One problem is that these functional helpers are not designed to handle async flow control.
While there are a dozen ways to handle flow control the easiest would probably be to use caolan/async module's map() method.
You're code would look something like:
var no = [78787878,78787879, 787878780];
async.map(no, function(cb) {
https.get('https://tsdrapi.uspto.gov/ts/cd/casestatus/'+no+'/info.xml', function (res) {
var response_data = '';
res.on('data', function (chunk) {
response_data += chunk;
});
res.on('end', function () {
parser.parseString(response_data, function (err, result) {
if (err) {
cb(err);
} else {
cb(null, result);
}
});
});
res.on('error', cb);
})
}, function(err, results) {
if(err) {
console.log("Error occured: ", err);
}
else {
console.log("Results(array): ", results);
}
});
The difference here is that async maps the array to a function with a callback. This way you can gather the response from each request into an array and fire a callback when each request has responded. If one of them error's out the process stops and fires the final callback where the error is logged(or you can write logic to handle another way).