I am struggling getting the coding of waiting for dependent services to prepare their data. I have a brain lock at the moment and I am not getting the behavior I expect. My expectation is that the home.ts page depends on the B service which in turn depends on the A service completing first. Additionally, I do not want to make home.ts page aware of A service (ideally).
I have made the following simplified attempts in stackblitz the 1st uses a Ready_P promise exposed by each service. However, I now realize that often depending on the injection mechanism it is undefined and thus the line this.BService.Ready_P.then().. fails in the constructor of home.ts.
What's a better/correct way of handling this?
// home.ts
import { CService } from './CService'; // 20220122
import { BService } from './BService'; // 20220122
//
@Component({
selector: 'home',
templateUrl: 'home.html',
styleUrls: ['./home.scss'],
// encapsulation: ViewEncapsulation.None
})
export class home {
constructor(
//
//
public CService: CService,
public BService: BService
) {
this.Init();
}
//
async Init() {
await this.CService.Load();
console.log('CService Ready from home');
await this.BService.Load();
console.log('BService Ready from home');
}
}
// C.ts
import { BService } from './BService'; // 20220122
/** # CService Depends on BService being data ready
* - 20220122 */
@Injectable()
export class CService {
// 20220122
constructor(
//
public BService: BService
) {
// this.BService.Load().then(() => {
// console.log('BService Ready from C');
// this.Load();
// });
}
//
//
Ready_P: Promise<boolean>;
//
async Load() {
await this.BService.Load()
// Do work
// if (this.Ready_P) return this.Ready_P; // to avoid repeated execution.
return (this.Ready_P = new Promise((resolve) => {
setTimeout(() => {
console.log('C Resolves');
resolve(true);
}, 1000);
}));
}
}
// B.ts
import { AService } from './AService'; // 20220122
/** # BService Depends on AService being data ready
* - 20220122 */
@Injectable()
export class BService {
// 20220122
constructor(
//
public AService: AService
) {
}
//
//
Ready_P: Promise<boolean>;
//
async Load() {
await this.AService.Load()
// Do work
// if (this.Ready_P) return this.Ready_P // to avoid repeated execution.
return (this.Ready_P = new Promise((resolve) => {
setTimeout(() => {
console.log('B Resolves');
resolve(true);
}, 2000);
}));
}
}
// A.ts is similar to C
The complete code can be found here.
NOTE: I would like my A, B, C services to be coded ideally using the same pattern. The services may or may not use http.get... or the like to complete and prepare some data. For simplicity I have used the setTimeout() to model that they will take some varying time to complete.
the expected sequence of execution
1. A Resolves
2. AService Ready from B
3. B Resolves
4. BService Ready from C
5. C Resolves
6. CService Ready from home
7. BService Ready from home
CodePudding user response:
You're on the right track. The reason you're seeing duplicates is that home calls init for B and C, and B inits A, and C inits B, so you end up with
home -> B -> A
home -> C -> B -> A
Notice that A and B will get initialized twice. If the classes really have this dependency, then add a side-effect to the init process the class knows it's done. Something like:
asnyc Load() {
if (this.wasLoaded) return Promise.resolve();
await this.otherService.Load;
this.wasLoaded = true;
}
One "side-effect" of the load might be that some instance data on the object is getting set. The presence of that data might be a good flag for whether Load needs to execute.
The other thing to fix is that you've successfully removed the async calls from the constructors of service A, B, C, but your home class calls its (async) Load() method from the constructor. Remove that, too.
Stylistically, the most modern syntax is presented in your home class. It's a style thing, but I'd copy that syntax (mark methods that use await as being async and use await instead of then()), to your other classes.
Lastly, in the async/await style, errors are caught with try/catch blocks, as in...
// in class A
async Load() {
if (this.dataWeGetFromB) return Promise.resolve();
try {
this.dataWeGetFromB = await this.BService.Load();
} catch (err) {
// handle err
}
}
CodePudding user response:
I wouldn't recommend using Promises for this purpose. Instead, Angular has a much better way to handle asynchronous code that depends on other code to finish.
Using your code, I'd write it this way:
Service A:
@Injectable()
export class AService {
// this service demonstrates dependance on http - an action that takes time to finish
constructor(private http: HttpClient) {}
public load() {
return this.http.get('https://jsonplaceholder.typicode.com/todos');
}
}
Note: For the purpose of the demo, I made this service depend on http - an action that takes time to finish.
Service B:
@Injectable()
export class BService {
private dataStoreExample: Subject<any> = new Subject<any>();
constructor(private AService: AService) {}
get dataFromServiceB() {
return this.dataStoreExample.asObservable();
}
public load() {
this.AService.load().subscribe((data) => {
console.log('A Resolves');
this.dataStoreExample.next(data);
});
}
}
Service B depends on Service A, and emits a value only when Service A finishes its job. home.ts in turn will receive the value only when it is emitted by B (again, after A finishes its job).
home.ts:
export class home {
constructor(
public Events: Events,
public BService: BService
) {
this.BService.dataFromServiceB.subscribe(
(data) => console.log('BService Ready from home, got data:', data),
(error) => console.log('BService FAIL from home, error:', error)
);
}
}
See here: https://stackblitz.com/edit/ionic-yvza2i?file=pages/home/home.ts
