【问题标题】:How to make every service wait for the services it needs to be ready?如何让每个服务都等待它需要准备好的服务?
【发布时间】:2022-01-24 06:31:04
【问题描述】:

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

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

有什么更好/正确的处理方式?

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

【问题讨论】:

  • 承诺和await 或.then()。
  • 鉴于我在这个 TS/JS hacking 中已经有一段时间了,我 100% 确信我永远不会完全掌握 Promises、Observable、Async/Await、rxjs 等事物的正确用法和本质, ... 但是,我有一个大致的理解,并且通过剪切/粘贴方法进行真正的编码,这在过去似乎对我有用。您能否很好地阐明您在 await 或以上的回答的意思。那么,我认为我没有将它们混入我的 plunkers 中。谢谢。
  • 嗯,到目前为止,您在问题中显示的所有代码都只是脚手架 - 它并没有真正做任何事情。教授整个通用主题(如承诺)比解决特定的现实世界编码问题然后让您从该解决方案中学习要困难 100-1000 倍。如果您使用真实代码而不是伪代码或代码脚手架来展示真实世界的编码问题,那么我很乐意看到提供解决方案。但是,我不能真正尝试在一个问题的一个答案的范围内教授 Promise 和 async/await 和 .then() 的一般概念。
  • 在不理解基本概念的情况下通过剪切/粘贴进行编码不会产生好的、可靠的代码。花时间学习基本概念是值得的。这将为您带来回报。
  • 并且,不要使用http.get() 向其他服务器发出http 请求。它的级别太低,不支持 Promise,并且需要大量额外的编码才能使其正常工作。相反,请获取像 got() 或 axios() 这样的库,以便更轻松地使用 Promise 进行编程。

标签: node.js angular typescript promise


【解决方案1】:

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

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

注意 A 和 B 将被初始化两次。如果类确实具有这种依赖关系,则向类知道它已完成的 init 进程添加一个副作用。比如:

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

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

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

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

最后,在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
  }
}

【讨论】:

  • 感谢@danh 抽出宝贵时间,我想我已经开始在 home.ts 中调用 Init() 来绕过,我认为构造函数不能用异步装饰的事实?在我上面stackblitz.com/edit/ionic-atyngp?file=pages%2Fhome%2Fhome.ts 分享的示例中,也许您正在寻找不同的 plunker。
  • 我已经引入了布尔标志,我已经在我的生产代码/项目中使用它来处理服务已经获取一些数据并准备好通过道具访问的事实。我通常会缓存那些数据/书籍以及什么不是,并通过服务道具公开我想要的东西。这是我的编辑减去尝试捕获。 stackblitz.com/edit/…
  • 我得到了预期的执行顺序 A Resolves, AService Ready from B, B Resolves, BService Ready from C, C Resolves, CService Ready from home, BService Ready from home,
  • 那么,一切都好吗?构造函数不能返回 Promise,因为它返回的是新实例。在其中等待实际上是在返回一个承诺。
【解决方案2】:

我不建议为此目的使用 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

【讨论】:

    猜你喜欢
    • 2021-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-18
    • 2018-12-03
    • 2019-03-15
    • 2019-02-09
    相关资源
    最近更新 更多