-2

I have a <slide-show> component that displays a list of images with timing, transitions etc. and emits a "finished" event when it's done.

Now I want to embed this component in another one that recurses in a tree of directories, sending a new list of images after each "finished" events. The code currently looks like this :

import { Component, Host, h, Prop, State, Event, EventEmitter } from '@stencil/core'
import * as path from 'path'
import isImage from 'is-image'

function waitForEvent(eventEmitter:EventEmitter<any>, eventType:string) {
  return new Promise(function (resolve) {
    eventEmitter.on(eventType, resolve)
  })
}

@Component({
  tag: 'slide-script',
  styleUrl: 'slide-script.css',
  shadow: true,
})
export class SlideScript {

  @Prop() src: string
  @State() images: Array<string>

  @Event() next: EventEmitter<boolean>

  componentWillLoad() {
    this.process(this.src)
  }

  async process(dir: string) {
    console.log(dir)
    return fetch(path.join('dir', dir))
      .then(response =>
        response.json()
          .then(data => {
            this.images = data.files.filter(isImage)
            this.images = this.images.map(im => path.join('img', dir, im))
            // the above will start/update the slideshow
            waitForEvent(this.next, "onFinished")
              .then(() => {
                data.subdirs.reduce(
                  async (prev: Promise<void>, sub: string) => {
                    await prev
                    return this.process(path.join(dir, sub))
                  },
                  Promise.resolve() // reduce initial value
                )
              })
          })
      )
  }

  handleFinished(e) {
    console.log('finished')
    this.next.emit(e)
  }

  render() {
    return (
      <Host>
        <slide-show images={this.images} onFinished={(e) => this.handleFinished(e)} />
      </Host>
    );
  }

}

the waitForEvent function does not work as stencil's EventEmitter is not a Node EventEmitter and has no .onmethod ...

How should I modify it ? or how to do it otherwise ? Thanks !

Dr. Goulu
  • 580
  • 7
  • 21
  • I think you're just missing a `return` in front of your `waitForEvent()` so you have `return waitForEvent(...)`. I don't follow what you're trying to do in your `.reduce()` loop. If there's a promise there, then you will need to return that promise too from the `.then()` handler that it's in. You insert a promise into a promise chain by returning it from the `.then()` handler that it's in. That causes the promise chain to wait for that promise to resolve at that point in the chain. – jfriend00 Jan 10 '21 at 19:37
  • Oh, you also need to be doing `return response.json().then()`. Same issue. ALWAYS return a promise from within a `.then()` handler unless you explicitly don't want the chain to wait for it. FYI, this promise chain could be flattened rather than nested and you could make the code look at lot simpler by using `await` instead of `.then()`. – jfriend00 Jan 10 '21 at 19:40
  • thanks @jfriend00 but the main problem is eventEmitter.on(eventType, resolve) is illegal (doesn't event compile) as an EventEmitter in stenciljs is not a Node EventEmitter , it doen't have a .on method ... – Dr. Goulu Jan 11 '21 at 11:29

1 Answers1

0

Ok, after roaming a bit on the Slack channel for StencilJS, I figured out I needed a deferas described in https://lea.verou.me/2016/12/resolve-promises-externally-with-this-one-weird-trick/

and the resulting code that successfully recurses in all directories is

import { Component, Host, h, Prop, State, Event, EventEmitter } from '@stencil/core'
import * as path from 'path'
import isImage from 'is-image'

function defer() {
  var deferred = {
    promise: null,
    resolve: null,
    reject: null
  };

  deferred.promise = new Promise((resolve, reject) => {
    deferred.resolve = resolve;
    deferred.reject = reject;
  });

  return deferred;
}


@Component({
  tag: 'slide-script',
  styleUrl: 'slide-script.css',
  shadow: true,
})
export class SlideScript {

  @Prop() src: string
  @State() images: Array<string>

  @Event() next: EventEmitter<boolean>

  componentWillLoad() {
    this.process(this.src)
  }

  private defer: any

  handleFinished(event) {
    console.log('finished', event)
    this.defer.resolve(true)
  }

  async process(dir: string) {
    console.log(dir)
    return fetch(path.join('dir', dir))
      .then(response =>
        response.json()
          .then(data => {
            this.images = data.files.filter(isImage)
            this.images = this.images.map(im => path.join('img', dir, im))
            this.defer = defer()
            return this.defer.promise.then(() =>
              data.subdirs.reduce((prev: Promise<void>, sub: string) =>
                prev.then(() =>
                  this.process(path.join(dir, sub)) // recurse
                ),
                Promise.resolve() // reduce initial value
              )
            )
          })
      )
  }

  render() {
    return (
      <Host>
        <slide-show images={this.images} onFinished={this.handleFinished.bind(this)} />
      </Host>
    );
  }
}
Dr. Goulu
  • 580
  • 7
  • 21