A way to avoid explicitly passing the “this” context?
While working on a new product I have created a back-end and front-end project. For front-end I am using Angular framework with Typescript. Below is a question due to me being new to the language (a few days old). My question is around callbacks and how to avoid the explicit pass with the "this" context. There are a few resources I have read which I will link.
Below I am implementing a wrapper for the HttpClient. The quick version is flow control with modals that follow a plugin architecture(backed by angular routing) is best complimented with a central delegation using observers and subscribers to broadcast the errors like 401 for a graceful re-entry(in my opinion) - we won't get into that though but was mention as context may help.
Here are the bare bones of my code:
The Wrapper =>
export class WebService {
constructor(private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>) { }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) =>
void, callBackInstance: any): void {
this.httpClient.post<T>(url, dataToPost).subscribe(
(data: T) => {
callBack.call(callBackInstance, data);
},
(error: HttpErrorResponse) => {
this.exceptionService.notify(error);
}
);
Now I am able to explicitly manage the "this" context for the callback using the .call() to insert it. I don't mind using this in any of your suggestions - however looking at the signature, you will find that the method requires you to pass in the "this" context you want(callbackInstance). This pushes some responsibility onto the caller of the method that I do not want. To me a class is very much like an array with the "this" as an initial displacement - since I am passing in the method for the callback; is there really no way to inspect that method to derive the appropriate "this"? Something along the lines of:
callbackInstance = callback.getRelativeContext();
callBack.call(callBackInstance, data);
This would eliminate the extra param making the method less error prone for my team to use.
Links to resources are welcome - but please try to narrow it down to the relevant part if possible.
Links:
For updating the "this" context
Parameter callbacks
EDIT:
From accepted answer I derived and placed in test case:
const simpleCallback = (response) => {holder.setValue(response); };
service.post<LoginToken>(Service.LOGIN_URL, '', simpleCallback);
angular typescript
add a comment |
While working on a new product I have created a back-end and front-end project. For front-end I am using Angular framework with Typescript. Below is a question due to me being new to the language (a few days old). My question is around callbacks and how to avoid the explicit pass with the "this" context. There are a few resources I have read which I will link.
Below I am implementing a wrapper for the HttpClient. The quick version is flow control with modals that follow a plugin architecture(backed by angular routing) is best complimented with a central delegation using observers and subscribers to broadcast the errors like 401 for a graceful re-entry(in my opinion) - we won't get into that though but was mention as context may help.
Here are the bare bones of my code:
The Wrapper =>
export class WebService {
constructor(private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>) { }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) =>
void, callBackInstance: any): void {
this.httpClient.post<T>(url, dataToPost).subscribe(
(data: T) => {
callBack.call(callBackInstance, data);
},
(error: HttpErrorResponse) => {
this.exceptionService.notify(error);
}
);
Now I am able to explicitly manage the "this" context for the callback using the .call() to insert it. I don't mind using this in any of your suggestions - however looking at the signature, you will find that the method requires you to pass in the "this" context you want(callbackInstance). This pushes some responsibility onto the caller of the method that I do not want. To me a class is very much like an array with the "this" as an initial displacement - since I am passing in the method for the callback; is there really no way to inspect that method to derive the appropriate "this"? Something along the lines of:
callbackInstance = callback.getRelativeContext();
callBack.call(callBackInstance, data);
This would eliminate the extra param making the method less error prone for my team to use.
Links to resources are welcome - but please try to narrow it down to the relevant part if possible.
Links:
For updating the "this" context
Parameter callbacks
EDIT:
From accepted answer I derived and placed in test case:
const simpleCallback = (response) => {holder.setValue(response); };
service.post<LoginToken>(Service.LOGIN_URL, '', simpleCallback);
angular typescript
1
Pass a callback that's already correctly bound, e.g. using an arrow function in the caller? Also you could return an observable and let the caller subscribe.
– jonrsharpe
Nov 26 '18 at 8:30
Would passing an observable back not make the caller responsible for the error delegation as well? Arrow function in callback...hmm I actually did not know I could do that. Let me try
– Nilo
Nov 26 '18 at 8:32
Not necessarily, you can use catch / pipe with catchError to handle that in the service.
– jonrsharpe
Nov 26 '18 at 8:34
Hmm I see...and this in turn would remove the need for a callback. Okay tweeking test cases to try it out.
– Nilo
Nov 26 '18 at 8:40
Thank you for your pointers. However after trying them both out I quite like the arrow method. I made a feeble attempt at it and only got the .bind method to work like this: service.post<LoginToken>(Service.LOGIN_URL, '', holder.setValue.bind(this)); However below hlfrmn has shown me quite a neat way of doing so. It is most clear that these issues stem from not knowing the language. Thanks to you both. Will accept hlfrmn answer as correct:
– Nilo
Nov 26 '18 at 9:03
add a comment |
While working on a new product I have created a back-end and front-end project. For front-end I am using Angular framework with Typescript. Below is a question due to me being new to the language (a few days old). My question is around callbacks and how to avoid the explicit pass with the "this" context. There are a few resources I have read which I will link.
Below I am implementing a wrapper for the HttpClient. The quick version is flow control with modals that follow a plugin architecture(backed by angular routing) is best complimented with a central delegation using observers and subscribers to broadcast the errors like 401 for a graceful re-entry(in my opinion) - we won't get into that though but was mention as context may help.
Here are the bare bones of my code:
The Wrapper =>
export class WebService {
constructor(private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>) { }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) =>
void, callBackInstance: any): void {
this.httpClient.post<T>(url, dataToPost).subscribe(
(data: T) => {
callBack.call(callBackInstance, data);
},
(error: HttpErrorResponse) => {
this.exceptionService.notify(error);
}
);
Now I am able to explicitly manage the "this" context for the callback using the .call() to insert it. I don't mind using this in any of your suggestions - however looking at the signature, you will find that the method requires you to pass in the "this" context you want(callbackInstance). This pushes some responsibility onto the caller of the method that I do not want. To me a class is very much like an array with the "this" as an initial displacement - since I am passing in the method for the callback; is there really no way to inspect that method to derive the appropriate "this"? Something along the lines of:
callbackInstance = callback.getRelativeContext();
callBack.call(callBackInstance, data);
This would eliminate the extra param making the method less error prone for my team to use.
Links to resources are welcome - but please try to narrow it down to the relevant part if possible.
Links:
For updating the "this" context
Parameter callbacks
EDIT:
From accepted answer I derived and placed in test case:
const simpleCallback = (response) => {holder.setValue(response); };
service.post<LoginToken>(Service.LOGIN_URL, '', simpleCallback);
angular typescript
While working on a new product I have created a back-end and front-end project. For front-end I am using Angular framework with Typescript. Below is a question due to me being new to the language (a few days old). My question is around callbacks and how to avoid the explicit pass with the "this" context. There are a few resources I have read which I will link.
Below I am implementing a wrapper for the HttpClient. The quick version is flow control with modals that follow a plugin architecture(backed by angular routing) is best complimented with a central delegation using observers and subscribers to broadcast the errors like 401 for a graceful re-entry(in my opinion) - we won't get into that though but was mention as context may help.
Here are the bare bones of my code:
The Wrapper =>
export class WebService {
constructor(private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>) { }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) =>
void, callBackInstance: any): void {
this.httpClient.post<T>(url, dataToPost).subscribe(
(data: T) => {
callBack.call(callBackInstance, data);
},
(error: HttpErrorResponse) => {
this.exceptionService.notify(error);
}
);
Now I am able to explicitly manage the "this" context for the callback using the .call() to insert it. I don't mind using this in any of your suggestions - however looking at the signature, you will find that the method requires you to pass in the "this" context you want(callbackInstance). This pushes some responsibility onto the caller of the method that I do not want. To me a class is very much like an array with the "this" as an initial displacement - since I am passing in the method for the callback; is there really no way to inspect that method to derive the appropriate "this"? Something along the lines of:
callbackInstance = callback.getRelativeContext();
callBack.call(callBackInstance, data);
This would eliminate the extra param making the method less error prone for my team to use.
Links to resources are welcome - but please try to narrow it down to the relevant part if possible.
Links:
For updating the "this" context
Parameter callbacks
EDIT:
From accepted answer I derived and placed in test case:
const simpleCallback = (response) => {holder.setValue(response); };
service.post<LoginToken>(Service.LOGIN_URL, '', simpleCallback);
angular typescript
angular typescript
edited Nov 26 '18 at 9:04
Nilo
asked Nov 26 '18 at 8:21
NiloNilo
134
134
1
Pass a callback that's already correctly bound, e.g. using an arrow function in the caller? Also you could return an observable and let the caller subscribe.
– jonrsharpe
Nov 26 '18 at 8:30
Would passing an observable back not make the caller responsible for the error delegation as well? Arrow function in callback...hmm I actually did not know I could do that. Let me try
– Nilo
Nov 26 '18 at 8:32
Not necessarily, you can use catch / pipe with catchError to handle that in the service.
– jonrsharpe
Nov 26 '18 at 8:34
Hmm I see...and this in turn would remove the need for a callback. Okay tweeking test cases to try it out.
– Nilo
Nov 26 '18 at 8:40
Thank you for your pointers. However after trying them both out I quite like the arrow method. I made a feeble attempt at it and only got the .bind method to work like this: service.post<LoginToken>(Service.LOGIN_URL, '', holder.setValue.bind(this)); However below hlfrmn has shown me quite a neat way of doing so. It is most clear that these issues stem from not knowing the language. Thanks to you both. Will accept hlfrmn answer as correct:
– Nilo
Nov 26 '18 at 9:03
add a comment |
1
Pass a callback that's already correctly bound, e.g. using an arrow function in the caller? Also you could return an observable and let the caller subscribe.
– jonrsharpe
Nov 26 '18 at 8:30
Would passing an observable back not make the caller responsible for the error delegation as well? Arrow function in callback...hmm I actually did not know I could do that. Let me try
– Nilo
Nov 26 '18 at 8:32
Not necessarily, you can use catch / pipe with catchError to handle that in the service.
– jonrsharpe
Nov 26 '18 at 8:34
Hmm I see...and this in turn would remove the need for a callback. Okay tweeking test cases to try it out.
– Nilo
Nov 26 '18 at 8:40
Thank you for your pointers. However after trying them both out I quite like the arrow method. I made a feeble attempt at it and only got the .bind method to work like this: service.post<LoginToken>(Service.LOGIN_URL, '', holder.setValue.bind(this)); However below hlfrmn has shown me quite a neat way of doing so. It is most clear that these issues stem from not knowing the language. Thanks to you both. Will accept hlfrmn answer as correct:
– Nilo
Nov 26 '18 at 9:03
1
1
Pass a callback that's already correctly bound, e.g. using an arrow function in the caller? Also you could return an observable and let the caller subscribe.
– jonrsharpe
Nov 26 '18 at 8:30
Pass a callback that's already correctly bound, e.g. using an arrow function in the caller? Also you could return an observable and let the caller subscribe.
– jonrsharpe
Nov 26 '18 at 8:30
Would passing an observable back not make the caller responsible for the error delegation as well? Arrow function in callback...hmm I actually did not know I could do that. Let me try
– Nilo
Nov 26 '18 at 8:32
Would passing an observable back not make the caller responsible for the error delegation as well? Arrow function in callback...hmm I actually did not know I could do that. Let me try
– Nilo
Nov 26 '18 at 8:32
Not necessarily, you can use catch / pipe with catchError to handle that in the service.
– jonrsharpe
Nov 26 '18 at 8:34
Not necessarily, you can use catch / pipe with catchError to handle that in the service.
– jonrsharpe
Nov 26 '18 at 8:34
Hmm I see...and this in turn would remove the need for a callback. Okay tweeking test cases to try it out.
– Nilo
Nov 26 '18 at 8:40
Hmm I see...and this in turn would remove the need for a callback. Okay tweeking test cases to try it out.
– Nilo
Nov 26 '18 at 8:40
Thank you for your pointers. However after trying them both out I quite like the arrow method. I made a feeble attempt at it and only got the .bind method to work like this: service.post<LoginToken>(Service.LOGIN_URL, '', holder.setValue.bind(this)); However below hlfrmn has shown me quite a neat way of doing so. It is most clear that these issues stem from not knowing the language. Thanks to you both. Will accept hlfrmn answer as correct:
– Nilo
Nov 26 '18 at 9:03
Thank you for your pointers. However after trying them both out I quite like the arrow method. I made a feeble attempt at it and only got the .bind method to work like this: service.post<LoginToken>(Service.LOGIN_URL, '', holder.setValue.bind(this)); However below hlfrmn has shown me quite a neat way of doing so. It is most clear that these issues stem from not knowing the language. Thanks to you both. Will accept hlfrmn answer as correct:
– Nilo
Nov 26 '18 at 9:03
add a comment |
1 Answer
1
active
oldest
votes
If you need to pass the context to the callback, then the callback itself will rely on that context:
function explicitContext(callback, context) {
const arg = 1;
callback.call(context, arg);
}
function implicitContext(callback) {
const arg = 1;
const someCleverContext = {importantVal: 42, importantFunc: () => {}};
callback.call(someCleverContext, arg);
}
Consider the usage, if we need to actually access the context in the callback:
function explicitUsage() {
const someCleverContext = {importantVal: 42, importantFunc: () => {}};
const callback = function(arg) {this.importantFunc(arg);}
explicitContext(callback, someCleverContext);
}
function implicitUsage() {
const callback = function(arg) {this.importantFunc(arg);}
implicitContext(callback);
}
In both cases we are actually leaking the details about the context and forcing some responsibility on the consumer! Now, there isn't a magic way to go around it if we really need to pass the context. The good news is, we probably don't need to pass the context in the first place.
export class WebService {
constructor(
private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>)
{ }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) => void): void {
this.httpClient.post<T>(url, dataToPost).subscribe(
(data: T) => {
callBack(data);
},
(error: HttpErrorResponse) => {
this.exceptionService.notify(error);
},
);
}
}
This way we can let the client code only care about the responseData, and if they need some clever context, they are free to bind it themselves:
function usage() {
let webService: WebService;
const simpleCallback = (response) => {console.log(response);} // can inline too
webService.post('/api', {data: 1}, simpleCallback);
const cleverContextCallback = function(response) {this.cleverLog(response)};
const cleverContext = {cleverLog: (data) => console.log(data)};
const boundCallback = cleverContextCallback.bind(cleverContext);
webService.post('/api', {data: 1}, boundCallback );
}
Having said all that, I would definitely recommend just returning the observable from your services.
export class WebService {
constructor(
private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>)
{ }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) => void): Observable<T> {
const observable = this.httpClient.post<T>(url, dataToPost);
// Note that httpClient.post automatically completes.
// If we were using some other library and we would want to close the observable ourselves,
// you could close the observable yourself here after one result:
if ('we need to close observable ourselves after a result') {
return observable.pipe(take(1));
}
if ('we need to handle errors') {
return observable.pipe(
catchError(error => {
this.exceptionService.notify(error);
if ('We can fallback') {
return of('Fallback Value');
} else {
throw new Error('OOPS');
}
}),
);
}
return observable;
}
}
Dealing with errors, closing and other chores inside the service would let the consumer of the service focus on the data from the response.
Thank you for this. This is what i was looking for. You have my gratitude!
– Nilo
Nov 26 '18 at 9:04
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53477080%2fa-way-to-avoid-explicitly-passing-the-this-context%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
If you need to pass the context to the callback, then the callback itself will rely on that context:
function explicitContext(callback, context) {
const arg = 1;
callback.call(context, arg);
}
function implicitContext(callback) {
const arg = 1;
const someCleverContext = {importantVal: 42, importantFunc: () => {}};
callback.call(someCleverContext, arg);
}
Consider the usage, if we need to actually access the context in the callback:
function explicitUsage() {
const someCleverContext = {importantVal: 42, importantFunc: () => {}};
const callback = function(arg) {this.importantFunc(arg);}
explicitContext(callback, someCleverContext);
}
function implicitUsage() {
const callback = function(arg) {this.importantFunc(arg);}
implicitContext(callback);
}
In both cases we are actually leaking the details about the context and forcing some responsibility on the consumer! Now, there isn't a magic way to go around it if we really need to pass the context. The good news is, we probably don't need to pass the context in the first place.
export class WebService {
constructor(
private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>)
{ }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) => void): void {
this.httpClient.post<T>(url, dataToPost).subscribe(
(data: T) => {
callBack(data);
},
(error: HttpErrorResponse) => {
this.exceptionService.notify(error);
},
);
}
}
This way we can let the client code only care about the responseData, and if they need some clever context, they are free to bind it themselves:
function usage() {
let webService: WebService;
const simpleCallback = (response) => {console.log(response);} // can inline too
webService.post('/api', {data: 1}, simpleCallback);
const cleverContextCallback = function(response) {this.cleverLog(response)};
const cleverContext = {cleverLog: (data) => console.log(data)};
const boundCallback = cleverContextCallback.bind(cleverContext);
webService.post('/api', {data: 1}, boundCallback );
}
Having said all that, I would definitely recommend just returning the observable from your services.
export class WebService {
constructor(
private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>)
{ }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) => void): Observable<T> {
const observable = this.httpClient.post<T>(url, dataToPost);
// Note that httpClient.post automatically completes.
// If we were using some other library and we would want to close the observable ourselves,
// you could close the observable yourself here after one result:
if ('we need to close observable ourselves after a result') {
return observable.pipe(take(1));
}
if ('we need to handle errors') {
return observable.pipe(
catchError(error => {
this.exceptionService.notify(error);
if ('We can fallback') {
return of('Fallback Value');
} else {
throw new Error('OOPS');
}
}),
);
}
return observable;
}
}
Dealing with errors, closing and other chores inside the service would let the consumer of the service focus on the data from the response.
Thank you for this. This is what i was looking for. You have my gratitude!
– Nilo
Nov 26 '18 at 9:04
add a comment |
If you need to pass the context to the callback, then the callback itself will rely on that context:
function explicitContext(callback, context) {
const arg = 1;
callback.call(context, arg);
}
function implicitContext(callback) {
const arg = 1;
const someCleverContext = {importantVal: 42, importantFunc: () => {}};
callback.call(someCleverContext, arg);
}
Consider the usage, if we need to actually access the context in the callback:
function explicitUsage() {
const someCleverContext = {importantVal: 42, importantFunc: () => {}};
const callback = function(arg) {this.importantFunc(arg);}
explicitContext(callback, someCleverContext);
}
function implicitUsage() {
const callback = function(arg) {this.importantFunc(arg);}
implicitContext(callback);
}
In both cases we are actually leaking the details about the context and forcing some responsibility on the consumer! Now, there isn't a magic way to go around it if we really need to pass the context. The good news is, we probably don't need to pass the context in the first place.
export class WebService {
constructor(
private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>)
{ }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) => void): void {
this.httpClient.post<T>(url, dataToPost).subscribe(
(data: T) => {
callBack(data);
},
(error: HttpErrorResponse) => {
this.exceptionService.notify(error);
},
);
}
}
This way we can let the client code only care about the responseData, and if they need some clever context, they are free to bind it themselves:
function usage() {
let webService: WebService;
const simpleCallback = (response) => {console.log(response);} // can inline too
webService.post('/api', {data: 1}, simpleCallback);
const cleverContextCallback = function(response) {this.cleverLog(response)};
const cleverContext = {cleverLog: (data) => console.log(data)};
const boundCallback = cleverContextCallback.bind(cleverContext);
webService.post('/api', {data: 1}, boundCallback );
}
Having said all that, I would definitely recommend just returning the observable from your services.
export class WebService {
constructor(
private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>)
{ }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) => void): Observable<T> {
const observable = this.httpClient.post<T>(url, dataToPost);
// Note that httpClient.post automatically completes.
// If we were using some other library and we would want to close the observable ourselves,
// you could close the observable yourself here after one result:
if ('we need to close observable ourselves after a result') {
return observable.pipe(take(1));
}
if ('we need to handle errors') {
return observable.pipe(
catchError(error => {
this.exceptionService.notify(error);
if ('We can fallback') {
return of('Fallback Value');
} else {
throw new Error('OOPS');
}
}),
);
}
return observable;
}
}
Dealing with errors, closing and other chores inside the service would let the consumer of the service focus on the data from the response.
Thank you for this. This is what i was looking for. You have my gratitude!
– Nilo
Nov 26 '18 at 9:04
add a comment |
If you need to pass the context to the callback, then the callback itself will rely on that context:
function explicitContext(callback, context) {
const arg = 1;
callback.call(context, arg);
}
function implicitContext(callback) {
const arg = 1;
const someCleverContext = {importantVal: 42, importantFunc: () => {}};
callback.call(someCleverContext, arg);
}
Consider the usage, if we need to actually access the context in the callback:
function explicitUsage() {
const someCleverContext = {importantVal: 42, importantFunc: () => {}};
const callback = function(arg) {this.importantFunc(arg);}
explicitContext(callback, someCleverContext);
}
function implicitUsage() {
const callback = function(arg) {this.importantFunc(arg);}
implicitContext(callback);
}
In both cases we are actually leaking the details about the context and forcing some responsibility on the consumer! Now, there isn't a magic way to go around it if we really need to pass the context. The good news is, we probably don't need to pass the context in the first place.
export class WebService {
constructor(
private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>)
{ }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) => void): void {
this.httpClient.post<T>(url, dataToPost).subscribe(
(data: T) => {
callBack(data);
},
(error: HttpErrorResponse) => {
this.exceptionService.notify(error);
},
);
}
}
This way we can let the client code only care about the responseData, and if they need some clever context, they are free to bind it themselves:
function usage() {
let webService: WebService;
const simpleCallback = (response) => {console.log(response);} // can inline too
webService.post('/api', {data: 1}, simpleCallback);
const cleverContextCallback = function(response) {this.cleverLog(response)};
const cleverContext = {cleverLog: (data) => console.log(data)};
const boundCallback = cleverContextCallback.bind(cleverContext);
webService.post('/api', {data: 1}, boundCallback );
}
Having said all that, I would definitely recommend just returning the observable from your services.
export class WebService {
constructor(
private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>)
{ }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) => void): Observable<T> {
const observable = this.httpClient.post<T>(url, dataToPost);
// Note that httpClient.post automatically completes.
// If we were using some other library and we would want to close the observable ourselves,
// you could close the observable yourself here after one result:
if ('we need to close observable ourselves after a result') {
return observable.pipe(take(1));
}
if ('we need to handle errors') {
return observable.pipe(
catchError(error => {
this.exceptionService.notify(error);
if ('We can fallback') {
return of('Fallback Value');
} else {
throw new Error('OOPS');
}
}),
);
}
return observable;
}
}
Dealing with errors, closing and other chores inside the service would let the consumer of the service focus on the data from the response.
If you need to pass the context to the callback, then the callback itself will rely on that context:
function explicitContext(callback, context) {
const arg = 1;
callback.call(context, arg);
}
function implicitContext(callback) {
const arg = 1;
const someCleverContext = {importantVal: 42, importantFunc: () => {}};
callback.call(someCleverContext, arg);
}
Consider the usage, if we need to actually access the context in the callback:
function explicitUsage() {
const someCleverContext = {importantVal: 42, importantFunc: () => {}};
const callback = function(arg) {this.importantFunc(arg);}
explicitContext(callback, someCleverContext);
}
function implicitUsage() {
const callback = function(arg) {this.importantFunc(arg);}
implicitContext(callback);
}
In both cases we are actually leaking the details about the context and forcing some responsibility on the consumer! Now, there isn't a magic way to go around it if we really need to pass the context. The good news is, we probably don't need to pass the context in the first place.
export class WebService {
constructor(
private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>)
{ }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) => void): void {
this.httpClient.post<T>(url, dataToPost).subscribe(
(data: T) => {
callBack(data);
},
(error: HttpErrorResponse) => {
this.exceptionService.notify(error);
},
);
}
}
This way we can let the client code only care about the responseData, and if they need some clever context, they are free to bind it themselves:
function usage() {
let webService: WebService;
const simpleCallback = (response) => {console.log(response);} // can inline too
webService.post('/api', {data: 1}, simpleCallback);
const cleverContextCallback = function(response) {this.cleverLog(response)};
const cleverContext = {cleverLog: (data) => console.log(data)};
const boundCallback = cleverContextCallback.bind(cleverContext);
webService.post('/api', {data: 1}, boundCallback );
}
Having said all that, I would definitely recommend just returning the observable from your services.
export class WebService {
constructor(
private httpClient: HttpClient,
private exceptionService: ExceptionService<Exception>)
{ }
public post<T>(url: string, dataToPost: any, callBack: (responseData: T) => void): Observable<T> {
const observable = this.httpClient.post<T>(url, dataToPost);
// Note that httpClient.post automatically completes.
// If we were using some other library and we would want to close the observable ourselves,
// you could close the observable yourself here after one result:
if ('we need to close observable ourselves after a result') {
return observable.pipe(take(1));
}
if ('we need to handle errors') {
return observable.pipe(
catchError(error => {
this.exceptionService.notify(error);
if ('We can fallback') {
return of('Fallback Value');
} else {
throw new Error('OOPS');
}
}),
);
}
return observable;
}
}
Dealing with errors, closing and other chores inside the service would let the consumer of the service focus on the data from the response.
edited Nov 26 '18 at 9:02
answered Nov 26 '18 at 8:50
hlfrmnhlfrmn
1,0321221
1,0321221
Thank you for this. This is what i was looking for. You have my gratitude!
– Nilo
Nov 26 '18 at 9:04
add a comment |
Thank you for this. This is what i was looking for. You have my gratitude!
– Nilo
Nov 26 '18 at 9:04
Thank you for this. This is what i was looking for. You have my gratitude!
– Nilo
Nov 26 '18 at 9:04
Thank you for this. This is what i was looking for. You have my gratitude!
– Nilo
Nov 26 '18 at 9:04
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53477080%2fa-way-to-avoid-explicitly-passing-the-this-context%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
Pass a callback that's already correctly bound, e.g. using an arrow function in the caller? Also you could return an observable and let the caller subscribe.
– jonrsharpe
Nov 26 '18 at 8:30
Would passing an observable back not make the caller responsible for the error delegation as well? Arrow function in callback...hmm I actually did not know I could do that. Let me try
– Nilo
Nov 26 '18 at 8:32
Not necessarily, you can use catch / pipe with catchError to handle that in the service.
– jonrsharpe
Nov 26 '18 at 8:34
Hmm I see...and this in turn would remove the need for a callback. Okay tweeking test cases to try it out.
– Nilo
Nov 26 '18 at 8:40
Thank you for your pointers. However after trying them both out I quite like the arrow method. I made a feeble attempt at it and only got the .bind method to work like this: service.post<LoginToken>(Service.LOGIN_URL, '', holder.setValue.bind(this)); However below hlfrmn has shown me quite a neat way of doing so. It is most clear that these issues stem from not knowing the language. Thanks to you both. Will accept hlfrmn answer as correct:
– Nilo
Nov 26 '18 at 9:03