I have a bluebird promise, which runs a check. If this check is true, it can continue on. However, if this check is false, it needs to spawn an asynchronous process, which it must wait to complete before continuing.
I have something like the following:
var foo = getPromise();
foo.spread( function (error, stdout, stdin) {
if (error) {
// ok, foo needs to hold on until this child exits
var child = spawn(fixerror);
child
.on('error', function (e) {
//I need to error out here
})
.on('close', function (e) {
//I need to have foo continue here
});
} else {
return "bar";
}
});
How would I go about doing that?
First off why does your .spread() handler take a callback with error as the first argument. That does not seem correct. An error should cause a rejection, not a fulfillment.
But, if that was indeed the way your code works, then you just need to return a promise from within your .spread() handler. That promise will then get chained to the original promise and you can then see when both are done:
getPromise().spread( function (error, stdout, stdin) {
if (error) {
// ok, foo needs to hold on until this child exits
return new Promise(function(resolve, reject) {
var child = spawn(fixerror);
child.on('error', function (e) {
//I need to error out here
reject(e);
}).on('close', function (e) {
// plug-in what you want to resolve with here
resolve(...);
});
});
} else {
return "bar";
}
}).then(function(val) {
// value here
}, function(err) {
// error here
});
But, probably, your .spread() handler should not have the error argument there and that should instead cause a rejection of the original promise:
getPromise().spread( function (stdout, stdin) {
// ok, foo needs to hold on until this child exits
return new Promise(function(resolve, reject) {
var child = spawn(fixerror);
child.on('error', function (e) {
//I need to error out here
reject(e);
}).on('close', function (e) {
// plug-in what you want to resolve with here
resolve(...);
});
});
}).then(function(val) {
// value here
}, function(err) {
// error here
});
wrap the spawn or any other alternative route in promise function, then keep the flow as, (sample code):
promiseChain
.spread((stderr, stdout, stdin) => stderr ? pSpawn(fixError) : 'bar')
.then(...
// example for promisified spawn code:
function pSpawn(fixError){
return new Promise((resolve, reject) => {
spawn(fixError)
.on('error', reject)
.on('close', resolve.bind(null, 'bar'))
})
}
Related
I'm having trouble to properly catch an error/reject in a promise chain.
const p1 = () => {
return new Promise((resolve, reject) => {
console.log("P1");
resolve();
});
};
const p2 = () => {
return new Promise((resolve, reject) => {
console.log("P2");
reject();
});
};
const p3 = () => {
return new Promise((resolve, reject) => {
console.log("P3");
resolve();
});
};
p1().catch(() => {
console.log("Caught p1");
}).then(p2).catch(() => {
console.log("Caught p2");
}).then(p3).catch(() => {
console.log("Caught p3");
}).then(() => {
console.log("Final then");
});
When the promise is rejected, the following .then still gets executed. In my understanding, when in a promise chain an error/reject happened, the .then calls that follow it are not executed any more.
P1
P2
Caught p2
P3
Final then
The rejection gets caught correctly, but why is "P3" logged after the catch?
What am I doing wrong?
To clarify #evolutionxbox, this is my expected result:
Promise.resolve().then(() => {
console.log("resolve #1");
return Promise.reject();
}).then(() => {
console.log("resolve #2");
return Promise.resolve();
}).then(() => {
console.log("resolve #3");
return Promise.resolve();
}).then(() => {
console.log("Final end");
}).catch(() => {
console.log("Caught");
});
This code works exactly like it should. And I can't see a difference to my code, except that I declared the functions separately.
The code above stops no matter where the promise is rejected.
Here is a synchronous equivalent of your code:
const f1 = () => {
console.log("F1");
};
const f2 = () => {
console.log("F2");
throw new Error();
};
const f3 = () => {
console.log("F3");
};
try {
f1();
} catch {
console.log("Caught f1");
}
try {
f2();
} catch {
console.log("Caught f2");
}
try {
f3();
} catch {
console.log("Caught f3");
}
console.log("Final code");
As you can see, that gives a matching result. Hopefully, looking at the synchronous code you would not be surprised why. In a try..catch you are allowed to attempt recovery. The idea is that the catch will stop the error propagation and you can hopefully continue further. Or if you do want to stop, you still have to explicitly throw again, for example:
doCode();
try {
makeCoffee();
} catch(err) {
if (err instanceof IAmATeapotError) {
//attempt recovery
makeTea();
} else {
//unrecoverable - log and re-throw
console.error("Fatal coffee related issue encountered", err);
throw err;
}
}
doCode();
This is also the purpose Promise#catch() serves - so you can attempt recovery or at least act when there was a problem. The idea is that after the .catch() you might be able to continue:
const orderPizza = (topping) =>
new Promise((resolve, reject) => {
if (topping === "pepperoni")
reject(new Error("No pepperoni available"));
else
resolve(`${topping} pizza`);
});
const makeToast = () => "toast";
const eat = food => console.log(`eating some ${food}`);
async function main() {
await orderPizza("cheese")
.catch(makeToast)
.then(eat);
console.log("-----");
await orderPizza("pepperoni")
.catch(makeToast)
.then(eat);
}
main();
In order to reject the promise chain from a .catch() you need to do something similar as a normal catch and fail at the error recovery by inducing another error. You can throw or return a rejected promise to that effect.
This code works exactly like it should. And I can't see a difference to my code, except that I declared the functions separately.
The code above stops no matter where the promise is rejected.
The second piece of code you show fails entirely after a reject because there are no other .catch()-es that are successful. It is basically similar to this synchronous code:
try {
console.log("log #1");
throw new Error();
console.log("log #2");
console.log("log #3");
console.log("Final end");
} catch {
console.log("Caught");
}
Thus if you do not want to recover early, you can also skip the .catch() instead of inducing another error.
Try this.
const p1 = (arg) => {
// Promise returns data in the respected arguments
return new Promise((resolve, reject) => {
// Data to be accessed through first argument.
resolve(arg);
});
};
const p2 = (arg) => {
return new Promise((resolve, reject) => {
// Data to be accessed through second argument.
reject(arg);
});
}
p1('p1').then(resolve => {
console.log(resolve + ' is handled with the resolve argument. So it is accessed with .then()');
}) // Since reject isn't configured to pass any data we don't use .catch()
p2('p2').catch(reject => {
console.log(reject + ' is handled with the reject argument. So it is accessed with .catch()');
}) // Since resolve ins't configured to pass any data we don't use .then()
// You would normally configure a Promise to return a value on with resolve, and access it with .then() when it completes a task successfully.
// .catch() would then be chained on to the end of .then() to handle errors when a task cannot be completed.
// Here is an example.
const p3 = () => {
return new Promise((resolve, reject) => {
var condition = true;
if (condition === true) {
resolve('P3');
} else {
reject('Promise failed!');
}
});
};
p3('p3').then(resolve => {
console.log(resolve);
}).catch(reject => {
console.log(reject);
})
You do not do anything wrong.
In your code you call the first promise p1. Then you write p1.catch(...).then(...).then(...).then(...). This is a chain which means that you should call then 3 times, because you called resolve method in the p1 promise (all these thens depend on the first promise).
When the promise is rejected, the following .then still gets executed.
Yes. Just to be accurate: the then and catch method calls are all executed synchronously (in one go), and so all promises involved are created in one go. It's the callbacks passed to these methods that execute asynchronously, as the relevant promises resolve (fullfill or reject).
In my understanding, when in a promise chain an error/reject happened, the .then calls that follow it are not executed any more.
This is not the case. The promise that a catch returns can either fullfill or reject depending on what happens in the callback passed to it, and so the callbacks further down the chain will execute accordingly when that promise resolves.
The rejection gets caught correctly, but why is "P3" logged after the catch?
As in your case the catch callback returns undefined (it only performs a console.log), its promise fullfulls! By consequence, the chained then callback -- on that promise -- is executed... etc.
If you want to "stop"
If you want to keep the chain as it is, but wish to have a behaviour where a rejection leads to no further execution of then or catch callbacks, then don't resolve the associated promise:
const stop = new Promise(resolve => null);
const p1 = () => {
return new Promise((resolve, reject) => {
console.log("P1");
resolve();
});
};
const p2 = () => {
return new Promise((resolve, reject) => {
console.log("P2");
reject();
});
};
const p3 = () => {
return new Promise((resolve, reject) => {
console.log("P3");
resolve();
});
};
p1().catch(() => {
console.log("Caught p1");
return stop; // don't resolve
}).then(p2).catch(() => {
console.log("Caught p2");
return stop;
}).then(p3).catch(() => {
console.log("Caught p3");
return stop;
}).then(() => {
console.log("Final then");
});
For the code below why does "A" and "B" get printed before the error 'a'
var a = async()=>{
return new Promise(async (resolve,reject)=>{
var v = await ab().catch((err)=>{
console.log("error")
return reject(err)
})
console.log("A")
console.log("B")
})
}
function ab(){
return new Promise((resolve,reject)=>{
return reject("a")
})
}
async function e(){
try{
await a()
}catch(e){
console.log(e)
}
}
e()
The output for the above code is
error
A
B
a
Shouldn't it be "error" followed by 'a'
Why are A and B getting printed?
Because there's no reason it shouldn't. The return is in a callback function, so it only returns from that callback function. It does reject the outer promise, but doing that doesn't prevent the remainder of the code in your promise executor (the function you pass new Promise) from continuing.
There are several problems with the code shown, though.
If you already have a promise, you don't need to create a new one. In a, for instance, you have the promise from ab, so there's no reason for new Promise. This is often called the explicit Promise creation antipattern, this question's answers go into detail.
Using await in a promise executor function generally doesn't make much sense and probably means the executor is doing too much. The purpose of a promise executor is to start an asynchronous process, not to wait for one.
Using .catch/.then/.finally inside an async function is generally not best practice; just use await and try/catch.
Your a function should probably be just:
const a = async () => {
try {
await ab();
// Or you might put the `console.log`s here, there are
// reasons for each, but it doesn't matter given we're
// rethrowing the error
} catch (err) {
console.log("error")
throw err;
}
console.log("A")
console.log("B")
};
That will reject the promise a returns when ab's promise rejects, using the same rejection reason, and terminate a's logic rather than allowing it to continue; if ab's promise doesn't reject, it will run the two console.log statements.
Live Example:
const a = async () => {
try {
await ab();
// Or you might put the `console.log`s here, there are
// reasons for each, but it doesn't matter given we're
// rethrowing the error
} catch (err) {
console.log("error")
throw err;
}
console.log("A")
console.log("B")
};
function ab() {
return new Promise((resolve, reject) => {
return reject("a");
});
}
async function e() {
try {
await a();
} catch (e) {
console.log(e);
}
}
e();
(tips: I'm using a nodejs framework eggjs based on koa)
The code below can run successfully.But if I put resolve() into callback function of fs.renameSync(), the promise won't return any thing, and the request keeps pending status.
What causes this? Does this have to do with the order of execution?
async uploadAsset(assetName, file) {
const { app } = this;
const logger = this.logger;
return new Promise(function(resolve, reject) {
fs.renameSync(file.filepath, `${app.config.multipart.projectAssetLocalPath}${assetName}`, err => {
if (err) {
logger.warn(err);
reject();
}
});
resolve();
});
}
renameSync is a synchronous version of rename. It does not accept a callback as an argument; it only accepts an oldpath and a newpath. If you pass a third argument, it will be ignored; the callback function you're passing is never called.
If you want this to be callback-based, use fs.rename instead, which does take a callback.
Your resolve is also outside the callback at the moment, when it should be inside:
async uploadAsset(assetName, file) {
const { app } = this;
const logger = this.logger;
return new Promise(function(resolve, reject) {
fs.rename(file.filepath, `${app.config.multipart.projectAssetLocalPath}${assetName}`, err => {
if (err) {
logger.warn(err);
reject();
}
resolve();
});
});
}
Or use fs.promises instead, to accomplish this without constructing the Promise yourself.
async uploadAsset(assetName, file) {
const { app } = this;
const logger = this.logger;
return fs.promises.rename(file.filepath, `${app.config.multipart.projectAssetLocalPath}${assetName}`)
.catch((err) => {
logger.warn(err);
throw new Error(err);
});
}
Why doesn't it run the code "console.log(err)" below? but instead returns "TypeError: Cannot create property 'uncaught' on string 'error in promise'"
function abc() {
throw "error in promise";
return 123;
};
abc().catch(function(err) {
console.log(err)
}).then ( abcMessage =>
console.log(abcMessage)
)
.then and .catch require a Promise to be constructed. You are not returning a promise. The promise callback (executor) takes two arguments, a resolver, and a rejecter. Depending on what happens in the code, you may need to call resolve if everything goes right, or reject if something goes wrong.
function abc() {
return new Promise(function(resolve, reject) {
reject(123)
});
};
abc()
.catch(err => {
console.log(err);
return err;
})
.then(abcMessage => {
console.log(abcMessage)
});
new Error("error in promise")
I'm using the Bluebird promise library. I have a chain of promisified functions like the following:
receiveMessageAsync(params)
.then(function(data)) {
return [data, handleMessageAsync(request)];
})
.spread(function(data, response) {
return [response, deleteMessageAsync(request)];
})
.spread(function(response, data) {
return sendResponseAsync(response);
})
.then(function(data) {
return waitForMessage(data);
})
.catch (function(err) {
// handle error here
});
Occasionally sendMessage will fail because, let's say, the server to respond to isn't available. I want the code to keep on trying to respond forever until it succeeds. You can't simply wrap the sendMessage in a catch because it doesn't actually throw an exception, I suppose, it calls the "error" function which, in this promisified code is the "catch" at the bottom. So there must be some way to "retry" send message in the "catch" section. The problem is that even if I retry in a loop in the "catch" I still have no way to jump up to the promise chain and execute the remaining promisified functions. How do I deal with this?
EDIT:
My retry for a HTTP post ended up looking like this:
function retry(func) {
return func()
.spread(function(httpResponse) {
if (httpResponse.statusCode != 200) {
Log.error("HTTP post returned error status: "+httpResponse.statusCode);
Sleep.sleep(5);
return retry(func);
}
})
.catch(function(err) {
Log.err("Unable to send response via HTTP");
Sleep.sleep(5);
return retry(func);
});
}
Here's a sample retry function (not yet tested):
function retry(maxRetries, fn) {
return fn().catch(function(err) {
if (maxRetries <= 0) {
throw err;
}
return retry(maxRetries - 1, fn);
});
}
The idea is that you can wrap a function that returns a promise with something that will catch and retry on error until running out of retries. So if you're going to retry sendResponseAsync:
receiveMessageAsync(params)
.then(function(data)) {
return [data, handleMessageAsync(request)];
})
.spread(function(data, response) {
return [response, deleteMessageAsync(request)];
})
.spread(function(response, data) {
return retry(3, function () { return sendResponseAsync(response); });
})
.then(function(data) {
return waitForMessage(data);
})
.catch (function(err) {
// handle error here
});
Since the retry promise won't actually throw until all retries have been exhausted, your call chain can continue.
Edit:
Of course, you could always loop forever if you preferred:
function retryForever(fn) {
return fn().catch(function(err) {
return retryForever(fn);
});
}
Here is a small helper that acts like then but retries the function.
Promise.prototype.retry = function retry(onFulfilled, onRejected, n){
n = n || 3; // default to 3 retries
return this.then(function(result) {
return Promise.try(function(){
return onFulfilled(result); // guard against synchronous errors too
}).catch(function(err){
if(n <= 0) throw err;
return this.retry(onFulfilled, onRejected, n - 1);
}.bind(this)); // keep `this` value
}.bind(this), onRejected);
};
Which would let you write your code prettier like:
receiveMessageAsync(params)
.then(function(data)) {
return [data, handleMessageAsync(request)];
})
.spread(function(data, response) {
return [response, deleteMessageAsync(request)];
})
.retry(function(response, data) {
return sendResponseAsync(response); // will retry this 3 times
})
.then(function(data) {
return waitForMessage(data);
})
.catch (function(err) {
// I don't like catch alls :/ Consider using `.error` instead.
});
I just released https://github.com/zyklus/promise-repeat, which retries a promise until it either times out or a maximum number of attempts are hit. It allows you to write:
receiveMessageAsync(params)
...
.spread(retry(
function(response, data) {
return sendResponseAsync(response);
}
))
...