问题
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:
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 byi
which is 1 at that point (so you see the first one lasting 3 seconds). The third picture follows the same logic.You call all
setTimeout
s at the same time usingi
as some sort of separator. To achieve what you need you should callsetTimeout
for picturen + 1
after thesetTimeout
for picturen
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