javascript promise chains not waiting for previous

2020-02-15 09:09发布

问题:

I'm struggling to understand javascript promises. I have a simple list and I want to highlight each element in turn by adding an "active" class, with a pause between each.

So I have the following mark up -

<h2 class="step step1">Promise</h2>
<h2 class="step step2">Promise</h2>
<h2 class="step step3">Promise</h2>
<h2 class="step step4">Promise</h2>

and 2 javascript promises. First to set the step -

function setStep(step) {
    return new Promise(function (resolve, reject) {
        console.log("Step ", step, new Date())
        document.querySelectorAll('.step').forEach(function (match) {
            match.classList.remove("active");
        });

        document.querySelector(".step" + step).classList.add("active");
        resolve(step);
    })
}

and a second that implements a wait -

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

and then I'm trying to chain it all together like this -

wait(1000)
    .then(function () { setStep(1) })
    .then(wait(1000))
    .then(function () { setStep(2) })
    .then(wait(1000))
    .then(function () { setStep(3) })

The first wait seem to execute as expected but then everything else appears to happen together and step 3 is highlighted, with no waits in between. The console messages all show the same timestamp.

What am I overlooking?

The complete code is as follows -

<style>
    .active {
        background-color: #C00;
        color: #FFF;
    }
</style>

<h2 class="step step1">Step 1</h2>
<h2 class="step step2">Step 2</h2>
<h2 class="step step3">Step 3</h2>

<script>
  const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

  function setStep(step) {
    return new Promise(function (resolve, reject) {
        console.log("Step ", step, new Date())
        document.querySelectorAll('.step').forEach(function (match) {
            match.classList.remove("active");
        });

        document.querySelector(".step" + step).classList.add("active");
        resolve(step);
    })
}

  wait(1000)
    .then(function () { setStep(1) })
    .then(wait(1000))
    .then(function () { setStep(2) })
    .then(wait(1000))
    .then(function () { setStep(3) })
</script>

回答1:

You need to pass a function reference to .then(), not a promise. So change this:

.then(wait(1000))

to this:

.then(wait.bind(null, 1000))

or this:

.then(() => wait(1000))

.then() only works properly when you pass it a function reference that it can call later. When you do .then(wait(1000)), you're executing wait(1000) immediately and passing the return value (a promise) to .then(). That is not how .then() works and you didn't want to be executing wait(1000) immediately either.


Or, you could make a new waitFn() that returns a function that could be used in the way you were doing (because it returns a function when called):

function waitFn(t) {
    return function() { return wait(t);}
}

Then, you could do this:

wait(1000)
  .then(function () { setStep(1) })
  .then(waitFn(1000))
  .then(function () { setStep(2) })
  .then(waitFn(1000))
.  then(function () { setStep(3) })