Typescript - setTimeout custom time from variable that is dynamic in for loop

荒凉一梦 提交于 2020-03-04 18:18:42

问题


So I have this function that has a timeout that changes every 3s:

 setActiveImage(promotions) {
    for (let i = 0; i <= promotions.length - 1; i++) {
      setTimeout(()=> {
        this.activeImage = 'http://myrul/public/Commercials/' + promotions[i].commercial_file[0].file;
      }, 3000*(i)); //CHANGE PICTURE EVERY 3s

    }
  }

Now I want change the time (3000) to a custom variable that I get from promotions. This is promotions:

So every picture, or instance of i, has it's own time.

Here is what I did:

for (let i = 0; i <= promotions.length - 1; i++) {

  var x = promotions[i].time; //GET TIME
  var y = +x; //TURN STRING TO NUMBER
  var z = y * 1000; //TURN SECOND INTO MILISECONDS

  var promoDisplayTime = z; //CUSTOM TIME

  setTimeout(()=> {
    this.activeImage = 'http://myurl/Commercials/' + promotions[i].commercial_file[0].file;
  }, promoDisplayTime*(i));

}

This in theory should work, but the timer goes way off. The first picture should last 4s, but lasts 3s. The second picture should last 3s but lasts 6s. Third picture should be 10s but lasts 4s...

I don't see what I am doing wrong. Why is the timer off even though I am sending the correct variable promoDisplayTime.


StackBlitz with dummy data: https://stackblitz.com/edit/angular-r6ejw9?file=src%2Fapp%2Fapp.component.html


回答1:


Here is one of the possible solutions with the async function and setTimeout().

With async function, we make sure that the next slide is not executed before the first one is.

The thing that you should watch out with the setTimeout is that the timer will execute the given function AFTER the time expires.

So the working example should be something like this:

activeImage: string = 'https://pngimage.net/wp-content/uploads/2018/06/start-png-.png';

  promotions = [
    {
      time: 6,
      pic: 'https://pngimage.net/wp-content/uploads/2018/06/1-an-png-1.png'
    },
    {
      time: 3,
      pic: 'https://upload.wikimedia.org/wikipedia/commons/1/10/MRT_Singapore_Destination_2.png'
    },
    {
      time: 4,
      pic: 'https://upload.wikimedia.org/wikipedia/commons/a/aa/L%C3%ADnea_3_CAMETRO.png'
    }
  ];

  ngOnInit() {
      this.setActiveImage();
  }

  setActiveImage() {
    let _this = this;
    countDown();

    function printer(i) {

      return new Promise(resolve => {
        _this.activeImage = _this.promotions[i].pic;
        setTimeout(function () {
          resolve(true);
        }, _this.promotions[i].time * 1000);
      });
    }

    async function countDown() {
      for (var i = _this.promotions.length -1; i >= 0; i--) {
        await printer(i);
      }
    }

  }

Live demo: https://stackblitz.com/edit/angular-qwtgm2




回答2:


I see two mistakes:

  1. You count i from 0. The result is that the first value (6 seconds) becomes 0 when you calculate timeout value so first function is executed almost immediately. Then the second value (3 seconds) is used for showing the second picture and is multiple by i which is 1 at that point (so you see the first one lasting 3 seconds). The third picture follows the same logic.

  2. You call all setTimeouts at the same time using i as some sort of separator. To achieve what you need you should call setTimeout for picture n + 1 after the setTimeout for picture n ends.




回答3:


per this answer Is setTimeout a good solution to do async functions with javascript?

setTimeout(function(){...}, 0) simply queues the code to run once the current call stack is finished executing.

so what's happens is the for loop finishes with promotions[i].time = 4 and the setTimeout piece of code runs with that value (at least this is what I see in your code in StackBlitz)...




回答4:


When working within an Angular context, I'd recommend to embrace to power of RxJs.

const MOCKED_DATA = [
  {
    time: "6",
    pic: "https://pngimage.net/wp-content/uploads/2018/06/1-an-png-1.png"
  },
  {
    time: "3",
    pic:
      "https://upload.wikimedia.org/wikipedia/commons/1/10/MRT_Singapore_Destination_2.png"
  },
  {
    time: "4",
    pic:
      "https://upload.wikimedia.org/wikipedia/commons/a/aa/L%C3%ADnea_3_CAMETRO.png"
  }
];

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  promotions$ = from(MOCKED_DATA).pipe(
    map(promotion => of(promotion.pic).pipe(delay(1000 * +promotion.time))),
    mergeAll(),
    startWith("https://pngimage.net/wp-content/uploads/2018/06/start-png-.png")
  );
}

and then in your template:

<img [src]="promotions$ | async" alt="Responsive image of item">

Live demo: https://stackblitz.com/edit/angular-xjcrgd?file=src/app/app.component.ts

Edit: I'm not sure whether the time is relative or not in your question but after reading it again I think it is. If that's the case, then you could build a remap function to have an absolute time like that:

const remapToAbsoluteTime = (data: ServerContent[]): LocalContent[] =>
  data.reduce((acc, x) => {
    if (!acc.length) {
      acc.push({
        ...x,
        time: +x.time
      });
    } else {
      acc.push({
        ...x,
        time: acc[acc.length - 1].time + +x.time
      });
    }

    return acc;
  }, []);

Then you'd have the photos ordered 1, 2, 3.

Live demo: https://stackblitz.com/edit/angular-6c4pqt?file=src/app/app.component.ts



来源:https://stackoverflow.com/questions/59930695/typescript-settimeout-custom-time-from-variable-that-is-dynamic-in-for-loop

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!