JSMonk / sweet-monads

The library which provides useful monads, interfaces, and lazy iterators.
MIT License
316 stars 20 forks source link

[Question] How to use async generators within asyncMap function of Maybe monad #18

Closed Lonli-Lokli closed 3 years ago

Lonli-Lokli commented 3 years ago

Assuming I have this code

 private async *mapDeleteOwners(): AsyncIterableIterator<EngagementOwnersTabState> {
    yield this.createState({ loading: true });

    await mergeM([this.state.selectedOwners, this.state.engagement]).asyncMap(async ([owners, engagement]) => {
      const result = await this.svc.deleteOwners(engagement.id, owners.map(owner => owner.id)).toPromise();
      if (result.isRight()) {
        const owners = await this.svc.getEngagementOwners(engagement.id).toPromise();
        yield this.createState({ owners: just<Either<ServerError, VendorOwnerDm[]>>(owners), canDelete: false }); // <-!!!!! HERE
      }  
    })
    this.state.selectedOwners.map(eSelection => eSelection)
    if (this.state.selectedOwners.isJust() && this.state.engagement.isJust()) {

    yield this.createState({ loading: false });
  }
}

Typescript complains that ts1163: yield expressions is only allowed in a generator body.

Is there any chance to use it this way?

JSMonk commented 3 years ago

Yes, but only with the next hack:

private async *mapDeleteOwners(): AsyncIterableIterator<EngagementOwnersTabState> {
    yield this.createState({ loading: true });

    yield* await mergeM([this.state.selectedOwners, this.state.engagement]).asyncMap(async function*([owners, engagement]) {
      const result = await this.svc.deleteOwners(engagement.id, owners.map(owner => owner.id)).toPromise();
      if (result.isRight()) {
        const owners = await this.svc.getEngagementOwners(engagement.id).toPromise();
        yield this.createState({ owners: just<Either<ServerError, VendorOwnerDm[]>>(owners), canDelete: false }); // <-!!!!! HERE
      }  
    })
    this.state.selectedOwners.map(eSelection => eSelection)
    if (this.state.selectedOwners.isJust() && this.state.engagement.isJust()) {

    yield this.createState({ loading: false });
  }
}

But, as for me, a better solution will look like this:

private async *mapDeleteOwners(): AsyncIterableIterator<EngagementOwnersTabState> {
    yield this.createState({ loading: true });
    const owners = await mergeM([this.state.selectedOwners, this.state.engagement])
        .asyncChain(async ([owners, engagement]) => 
                 mergeM([
                    await this.svc.deleteOwners(engagement.id, owners.map(owner => owner.id)).toPromise(),
                    engagement
                 ])
        )
       // If the result of the previous chain will be left - this chain will be not executed
      .asynChain(([owners, engagement])  => this.svc.getEngagementOwners(engagement.id).toPromise())
    if (owners.isRight()) {
       yield this.createState({ owners: just<Either<ServerError, VendorOwnerDm[]>>(owners.value), canDelete: false }); // <-!!!!! HERE
    }
    this.state.selectedOwners.map(eSelection => eSelection)
    if (this.state.selectedOwners.isJust() && this.state.engagement.isJust()) {

    yield this.createState({ loading: false });
  }
}
Lonli-Lokli commented 3 years ago

Thanks for help, actually 1st one is more simple as we can yield from any point inside