Reputation: 611
I have the following function in a link builder which is using a configuration service which returns a promise with some enviroment parameters:
_createLink: function (label, args) {
return LinkBuilder.builder()
.withLabel(label)
.withUrl(ConfigService.getConfig()
.then((env) => env.baseUrl
+ TranslationService.instant('MY.URL', args)))
.buildLink();
},
A few explanations:
withUrl(...)
does the following:withUrl: function (param) { if (typeof param === 'string') { builder.value.linkUrl = param; } else if (typeof param === 'function') { builder.value.linkCalculationFunction = param; } else if (typeof param === 'object' && typeof param.lazyBuildUrl === 'function') { builder.value.linkCalculationFunction = param.lazyBuildUrl(); } else if (typeof param === 'object' && typeof param.then === 'function') { builder.value.linkCalculationFunction = () => param; } else { throw new Error('invalid url param ' + param); } return builder; },
withLabel(label)
will set a label which will be displayed as the URL textwithLabel: function (label) {builder.value.labelKey = label; return builder;}
.buildLink()
is just returning builder.value
, with the URL and all other params:buildLink: function () {return builder.value;}
TranslationService
will find the 'MY.URL'
entry in a JSON file with translations. The value will be something like 'http://www.myserver.com#name={{name}}&age={{age}}'
. The name
and age
parameters from args
will be inserted there.I need to encode those args
using an external service which also returns a promise and then to append the return value to my link. That external service will return something like 'data': {'encodedId': '123-456-789'}
, and my final link should be: http://www.myserver.com#encodedId=123-456-789
. Here is what I did:
In the JSON file with translations I removed the parameters from the entry, so now it's only:
'http://www.myserver.com#'
The _createLink
function now looks like this:
_createLink: function (label, args) {
let content = {
'name': args.name,
'age': args.age
};
return EncodingService.saveContent(content)
.then((data) => {
return LinkBuilder.builder()
.withLabel(label)
.withUrl(ConfigService.getConfig()
.then((env) => env.baseUrl
+ TranslationService.instant('MY.URL')
+ 'encodedId=' + data.encodedId))
.buildLink();
},
When I place a breakpoint at the last then
line, I see that I have the correct data.encodedId
, the correct env.baseUrl
and the TranslationService
is also correctly returning the JSON entry. But for some reason, I don't see the link in my page anymore. I guess that I'm doing something wrong with chaining the .then
s, but I'm not sure what.
Here is the third version, which still doesn't work:
_createLink: function (label, args) {
let content = {
'name': args.name,
'age': args.age
};
return $q.all([
EncodingService.saveContent(content),
ConfigService.getConfig()
])
.then((data) => {
let url = data[1].baseUrl
+ TranslationService.instant('MY.URL')
+ 'encodedId=' + data[0].encodedId;
return LinkBuilder.builder()
.withLabel(label)
.withUrl(url)
.buildLink();
},
Again, I placed the breakpoint at the last return
statement and there I have the url
formed correctly...
This function is used like this:
getLeadLink: function (label, access, address) {
return myService._createLink(label, someService._createArguments(access, address));
},
...and getLeadLink
is used in a component like this:
this.$onInit = () => {
...
this.leadLink = OtherService.getLeadLink('LINEINFO.LEAD.LINK', someData.access, someData.address);
...
};
...and leadLink
is then displayed in the HTML file.
Note: None of these things was modified, so the usage of _createLink
and the displaying of the link is still the same as it was before.
Upvotes: 0
Views: 107
Reputation: 350034
The third attempt is going in the right direction.
There is however no way you can get the future URL now. And so any attempt that in the end expects a simple function call to return the URL cannot be right. Such a function (like _createLink
) is deemed to return before the asynchronous parts (i.e. any then
callbacks) are executed. In your third attempt you improved things so that at least _createLink
will return a promise.
Now remains to await the resolution of that promise.
So, any wrapping function execution around _createLink
, should take into account the asynchronous pattern:
getLeadLink: function (label, access, address) {
return myService._createLink(label, someService._createArguments(access, address));
},
This is fine, but realise that _createLink
does not return the URL, but a promise. And so also getLeadLink
will return a promise.
You write that you want to display the URL, so you would do something like this (I don't know how you will display, so this is simplified):
OtherService.getLeadLink('LINEINFO.LEAD.LINK', someData.access, someData.address).then(function(builder) {
var $link = $("<a>").attr("href", builder.value.linkUrl)
.text(builder.value.labelKey);
$(document).append($link);
});
The key message is: don't try to get the URL as the return value of a function. It does not matter how many wrappers you write around the core logic; it will remain an asynchronous task, so you need to get and display the URL asynchronously as well. You'll need to do it in a then
callback, or use await
syntax, which makes anything following it asynchronous code.
Upvotes: 2