Entering testUpdateAttributes
shouldBePromise undefined
Leaving testUpdateAttributes
updatedThing:
thing {__cachedRelations: Object, __data: Object, __dataSource: undefined, __strict: false, __persisted: true, …}
https://github.com/htmlauthor/loopback-sandbox
As you can see "Leaving testUpdateAttributes" was out before "updatedThing: " which means await shouldBePromise; statement is ignored and not waited for. Also shouldBePromise value is undefined where it should be Promise if updateAttributes would return Promise.
$ node -e 'console.log(process.platform, process.arch, process.versions.node)'
darwin x64 8.9.4
$ npm ls --prod --depth 0 | grep loopback
[email protected] /Users/justinaskabasinskas/Projects/loopback-sandbox
├── [email protected]
├── [email protected]
├── [email protected]
I am cross-posting the relevant remote method here:
Thing.testUpdateAttributes = async (idToUpdate, dataToUpdate, callback) => {
console.log('Entering testUpdateAttributes');
let thing = await app.models.thing.findById(idToUpdate);
const shouldBePromise = thing.updateAttributes(dataToUpdate, (error, updatedThing) => {
if (error) {
console.error(error);
} else {
console.log('updatedThing: ', updatedThing);
}
});
console.log('shouldBePromise', shouldBePromise);
await shouldBePromise;
console.log('Leaving testUpdateAttributes');
};
TL;DR: Please don't mix promises and callbacks. Pick one async flow control style and stick to it.
Longer version:
LoopBack methods do not return a promise when a callback argument was provided. Only when there was no callback argument, we create an internal callback that's resolving/rejecting a new Promise object.
Conceptually:
PersistedModel.prototype.updateAttributes = function(data, callback) {
if (!callback) {
callback = createPromiseCallback();
}
this.dataSource.connector.patchAttributes(this.id, data, callback);
return callback.promise;
}
Below is a fixed version of your remote method:
Thing.testUpdateAttributes = async (idToUpdate, dataToUpdate) => {
console.log('Entering testUpdateAttributes');
const thing = await app.models.thing.findById(idToUpdate);
try {
const shouldBePromise = thing.updateAttributes(dataToUpdate);
console.log('shouldBePromise', shouldBePromise);
const updatedThing = await shouldBePromise;
console.log('updatedThing: ', updatedThing);
} catch (error) {
console.error(error);
}
console.log('Leaving testUpdateAttributes');
};
The steps to reproduce now print the following test to console (I am running on Node.js 10.1.0):
Entering testUpdateAttributes
shouldBePromise Promise {
_bitField: 0,
_fulfillmentHandler0: undefined,
_rejectionHandler0: undefined,
_promise0: undefined,
_receiver0: undefined }
updatedThing: { name: 'foobar',
size: 0,
creationTime: 2018-05-02T17:43:18.168Z,
id: 1 }
Leaving testUpdateAttributes
I cannot tell what's your original intention. If I was writing a similar method myself, I would write it as follows:
Thing.testUpdateAttributes = async(idToUpdate, dataToUpdate) => {
const thing = await app.models.thing.findById(idToUpdate);
const updatedThing = await thing.updateAttributes(dataToUpdate);
console.log('updatedThing: ', updatedThing);
return updatedThing;
};
Two things to notice:
return callback(error).I am closing this issue as answered.
Most helpful comment
TL;DR: Please don't mix promises and callbacks. Pick one async flow control style and stick to it.
Longer version:
LoopBack methods do not return a promise when a callback argument was provided. Only when there was no callback argument, we create an internal callback that's resolving/rejecting a new Promise object.
Conceptually:
Below is a fixed version of your remote method:
The steps to reproduce now print the following test to console (I am running on Node.js 10.1.0):
I cannot tell what's your original intention. If I was writing a similar method myself, I would write it as follows:
Two things to notice:
return callback(error).