如何让每一个服务都等待它需要的服务就绪?

How to make every service wait for the services it needs to be ready?

我正在努力获取等待相关服务准备其数据的编码。我现在有一个脑锁,我没有得到我期望的行为。 我的期望是 home.ts 页面依赖于 B 服务,而 B 服务又依赖于首先完成的 A 服务。此外,我不想让 home.ts 页面知道 A 服务(理想情况下)。

我在 stackblitz 中进行了以下简化尝试,第一次使用每个服务公开的 Ready_P 承诺。但是,我现在意识到,通常取决于注入机制,它是未定义的,因此行 this.BService.Ready_P.then().. 在 home.ts.

的构造函数中失败

better/correct 处理此问题的方法是什么?

// 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

完整代码可见here.

注意:我希望我的 A、B、C 服务使用相同的模式进行理想编码。服务可能会或可能不会使用 http.get... 或类似的方法来完成和准备一些数据。为简单起见,我使用 setTimeout() 来模拟它们将花费一些不同的时间来完成。

预期的执行顺序

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

我不建议为此目的使用 Promises。相反,Angular 有更好的方法来处理依赖于其他代码才能完成的异步代码。

使用你的代码,我会这样写:

服务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');
  }
}

注意:出于演示的目的,我使此服务依赖于 http - 一个需要时间才能完成的操作。

服务 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);
    });
  }
}

服务 B 依赖于服务 A,并且仅当服务 A 完成其工作时才发出一个值。 home.ts 反过来只有当 B 发出它时才会收到该值(同样,在 A 完成其工作之后)。

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)
      );
  }
}

看这里:https://stackblitz.com/edit/ionic-yvza2i?file=pages/home/home.ts

你走在正确的轨道上。你看到重复项的原因是 home 调用 B 和 C 的 init,B 初始化 A,C 初始化 B,所以你最终得到

home -> B -> A
home -> C -> B -> A

注意 A 和 B 将被初始化两次。如果 classes 确实有这种依赖性,那么将 side-effect 添加到 init 进程中 class 知道它已经完成。类似于:

asnyc Load() {
  if (this.wasLoaded) return Promise.resolve();
  await this.otherService.Load();
  this.wasLoaded = true;
}

一个“side-effect”负载可能是正在设置对象上的某些实例数据。该数据的存在可能是 Load 是否需要执行的一个很好的标志。

另一件要解决的事情是,您已经成功地从服务 A、B、C 的构造函数中删除了异步调用,但是您的 home class 调用了它的(异步)Load() 构造函数中的方法。把它也去掉。

在文体上,最现代的语法出现在您的 home class 中。这是一种风格,但我会将该语法(将使用 await 的方法标记为 async 并使用 await 而不是 then())复制到您的另一个 classes.

最后,在 async/await 风格中,错误被 try/catch 块捕获,如...

// in class A
async Load() {
  if (this.dataWeGetFromB) return Promise.resolve();
  try {
    this.dataWeGetFromB =  await this.BService.Load();
  } catch (err) {
    // handle err
  }
}