【问题标题】:Difference between run sequence promises by creating a new promise then run and creating all promises then run each通过创建一个新的 Promise 然后运行并创建所有 Promise 然后运行每个 Promise 来区分运行顺序 Promise
【发布时间】:2017-09-07 05:18:56
【问题描述】:

请帮我解释一下为什么日志结果有两种不同:

方式1:每1秒顺序记录一次

方式 2:1 秒后记录所有元素。

// Way 1
let sequence = Promise.resolve();
[1,2,3,4].forEach((val)=> {
    sequence = sequence.then(()=> {
        return new Promise(resolve => setTimeout(resolve, 1000, val))
    }).then(console.log);
})


//Way 2
let sequence2 = Promise.resolve();
[5,6,7,8].map(val => {
    return new Promise(resolve => setTimeout(resolve, 1000, val));
}).forEach(promise => {
    sequence2 = sequence2.then(() => promise).then(console.log);
});

已编辑:以方式 2 描述错误的日志结果

【问题讨论】:

  • 您的 Map 函数是同步的,因此当场执行所有操作。我在这里执行的意思只是将你的 Promise 添加到未来的调用堆栈中,然后继续前进,因为 Promise 是异步的。

标签: javascript asynchronous promise sequence


【解决方案1】:

您已按“方式 1”对计时器进行排序(它们每一个触发一秒钟)并以“方式 2”并行运行您的计时器,因此所有计时器几乎同时触发。下面是更详细的解释:

在“方式 1”中,您使用 sequence = sequence.then() 创建一个承诺链,并从序列中调用 setTimeout()。因此,计时器 2 直到计时器 1 触发后才会启动,依此类推。您将让每个单独的计时器真正按顺序运行,每次触发大约相隔 1 秒。

在“方式 2”中,您在 .map() 中一次启动所有计时器,因此所有计时器并行运行,而不是按顺序运行。然后你试图用你的sequence2 = sequence2.then() 循环强制它们进入一个序列,但是异步操作已经并行启动了,所以所有这个顺序循环实际上并没有比Promise.all() 在时间方面完成更多的事情。

如果您运行这两个 sn-ps 中的每一个,它们将准确记录计时器何时触发,您可以看到两者之间的区别。

在这里,在记录每个计时器时间序列的“方式 1”版本中,您可以看到计时器间隔大约 1 秒触发:

// way 1
let startTime = Date.now();

function log() {
    let args = Array.from(arguments);
    // calc time since startTime
    let delta = (Date.now() - startTime) / 1000;
    args.unshift(delta + ": ");
    console.log.apply(console, args);
}

function delay(t, val) {
    return new Promise(resolve => {
        setTimeout(() => {
            log("timer fire");
            resolve(val);           
        }, t);
    });
}

// Way 1
let sequence = Promise.resolve();
[1,2,3,4].forEach((val)=> {
    sequence = sequence.then(()=> {
        return delay(1000, val);
    }).then(console.log);
});
sequence.then(() => {
    log("all done");
})

在这里,在记录每个计时器时间序列的“方式 2”版本中,您可以看到计时器几乎同时触发:

// way 2
let startTime = Date.now();

function log() {
    let args = Array.from(arguments);
    // calc time since startTime
    let delta = (Date.now() - startTime) / 1000;
    args.unshift(delta + ": ");
    console.log.apply(console, args);
}

function delay(t, val) {
    return new Promise(resolve => {
        setTimeout(() => {
            log("timer fire");
            resolve(val);           
        }, t);
    });
}

//Way 2
let sequence2 = Promise.resolve();
[5,6,7,8].map(val => {
    return new delay(1000, val);
}).forEach(promise => {
    sequence2 = sequence2.then(() => promise).then(console.log);
});

sequence2.then(() => {
    log("all done");
});

在运行每个 sn-p 时,并不是特别是获得“全部完成”消息所需的时间。第一个序列化四个一秒定时器,所以它需要 运行约 4 秒。第二个并行运行所有计时器,因此运行大约需要 1 秒。


补充说明:

当你这样做时:

let arrayOfPromoises = [5,6,7,8].map(val => {
    return new Promise(resolve => setTimeout(resolve, 1000, val));
});

该代码连续执行四次return new Promise(resolve => setTimeout(resolve, 1000, val));,一个接一个,没有延迟。由于每次执行该代码时,它都会创建一个新的 Promise 对象,因此您最终会得到一个包含四个 Promise 的数组。

现在,在该代码中,您有以下内容:

new Promise(resolve => setTimeout(resolve, 1000, val));

立即调用 Promise 执行器函数(即您传递给 Promise 构造函数的回调函数)。没有等待。因此,您不仅创建了四个 Promise,而且还启动了四个同时​​运行的 Promise。这些计时器已经启动。 sequence2 = sequence2.then() 循环的任何结构都不会改变这一点。计时器已在运行。

【讨论】:

  • "顺序循环实际上并没有比Promise.all() 完成更多的事情" - 它们的行为在实现值和拒绝处理方式之间有所不同。我认为顺序循环是 more 错误的 :-)
  • @Bergi - 我只是指时间方面。显然,其他一切都不同。编辑澄清。
  • 我真的不明白为什么 .map() 并行运行整个承诺(请参阅@Shyam Babu 的回答)
  • @windluffy - .map() 是一个同步函数。您创建的 Promise 是非阻塞的,因此它们只是创建 Promise 并立即返回,并在一段时间后解决。因此,您立即启动所有计时器并以一组代表已启动的异步操作的 Promise 结束。我不遵循你不理解的部分。你确实意识到setTimeout() 是非阻塞的,对吧?它为将来的某个时间安排计时器,然后立即返回。
  • 我知道 setTimout() 是非阻塞的。在方式 2 中,我真的尝试“用你的 sequence2 = sequence2.then() 循环强制它们进入一个序列”,但我仍然不明白为什么“异步操作已经并行启动”
【解决方案2】:

要理解这一点,您需要了解 promise 只是等待某事解决,因此在获得解决之前,以下 (.then) 代码链不会执行

首先,方法让我们使用不同的变量而不是相同的序列变量,然后代码就变成了。

var firstSequence = Promise.resolve()
                        .then(()=> {return new Promise(resolve => setTimeout(resolve, 1000, 1))})
                        .then(console.log);
var secondSequence = firstSequence
                        .then(()=> {return new Promise(resolve => setTimeout(resolve, 1000, 2))})
                        .then(console.log);
var thirdSequence = secondSequence
                        .then(()=> {return new Promise(resolve => setTimeout(resolve, 1000, 3))})
                        .then(console.log);
var foruthSequence = thirdSequence
                        .then(()=> {return new Promise(resolve => setTimeout(resolve, 1000, 4))})
                        .then(console.log);                       

从这里你可以看到在 promise 被解决之前超时调用不会发生。因此它们似乎是连续的,并且它们一直被推动等待一秒钟。

但第二种形式等价于这个。

function makePromise(val) {
    return new Promise(resolve => setTimeout(resolve, 1000, val)); 
}
var storageArray  = [makePromise(1), makePromise(2), makePromise(3), 
makePromise(4)];

我们已经一次性将所有的 setimout 调用推送到一起,所以一秒钟后所有的 Promise 都已经解决了。所以 (.then) 不需要等待。

 var firstSequence = Promise.resolve()
                    .then(()=> {return storageArray[0]})
                    .then(console.log);
 var secondSequence = firstSequence
                    .then(()=> {return storageArray[1]})
                    .then(console.log);
 var thirdSequence = secondSequence
                    .then(()=> {return storageArray[2]})
                    .then(console.log);
 var foruthSequence = thirdSequence
                    .then(()=> {return storageArray[3]})
                    .then(console.log); 

因此,即使它们看起来与第一个语法相似,promise 都已经解决,因此无需等待。

【讨论】:

  • 虽然您已经转换了 sn-p 代码,但我不明白为什么“一次将所有 setimout 调用推到一起”。我看到 settimeout 在单独的承诺中被调用
猜你喜欢
  • 2017-11-16
  • 1970-01-01
  • 2018-04-18
  • 1970-01-01
  • 2018-07-12
  • 1970-01-01
  • 2022-01-20
  • 2016-02-17
  • 1970-01-01
相关资源
最近更新 更多