Javascript:在没有库的情况下连续(或顺序)运行异步任务

Kam*_*n J 5 javascript promise async-await es6-promise

我想在循环中运行一些异步任务,但它应该按顺序执行(一个接一个)。它应该是普通的 JS,不带有任何库。

\n\n
var doSome = function(i) {\n   return Promise.resolve(setTimeout(() => {\n      console.log('done... ' + i)\n   }, 1000 * (i%3)));\n}\n\nvar looper = function() {\n   var p = Promise.resolve();\n\n   [1,2,3].forEach((n) => {\n      p = p.then(() => doSome(n))\n   })\n\n   return p;\n}\n\nlooper();\n
Run Code Online (Sandbox Code Playgroud)\n\n

电流输出:

\n\n
calling for ...1\ncalling for ...2\ncalling for ...3\nPromise\xc2\xa0{<resolved>: 8260}\ndone... 3\ndone... 1\ndone... 2\n
Run Code Online (Sandbox Code Playgroud)\n\n

预期输出:

\n\n
calling for ...1\ncalling for ...2\ncalling for ...3\nPromise\xc2\xa0{<resolved>: 8260}\ndone... 1\ndone... 2\ndone... 3\n
Run Code Online (Sandbox Code Playgroud)\n\n

注意:如果您尝试过并且它按预期工作,请回答

\n

Ale*_*ied 3

因此,从您下面的评论来看,我认为您自己的示例代码与您的描述不太匹配。我认为您想要的示例更接近以下代码片段:

var doSome = function(i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`Completing ${i}`), 1000*(i%3))
  });
}

var looper = function() {
  [1,2,3].forEach((n) => {
    doSome(n).then(console.log);
  });
}

looper();
Run Code Online (Sandbox Code Playgroud)

这里,数组[1, 2, 3]被迭代,并为每个数组生成一个异步过程。当每个异步进程完成时,我们会.then在它们上并控制台记录它们的解析结果。

那么,现在的问题是如何最好地对结果进行排队?下面,我将它们存储到一个数组中,然后利用async/await暂停结果的执行,直到它们按顺序完成。

// This is probably what you want
var doSome = function(i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`Completing ${i}`), 1000*(i%3))
  });
}

var looper = async function() {
  const nums = [1,2,3];
  const promises = []
  nums.forEach((n) => {
    console.log(`Queueing ${n}`);
    promises.push(doSome(n));
  });
  for (let promise of promises) {
    const result = await promise;
    console.log(result);
  }
}

looper();
Run Code Online (Sandbox Code Playgroud)

现在,我们可以消除循环,只在最后一个完成后执行一个循环:

// Don't use this-- it is less efficient
var doSome = function(i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`Completing ${i}`), 1000*(i%3))
  });
}

var looper = async function() {
  const nums = [1,2,3];
  const promises = [];
  for (let n of nums) {
    console.log(`Queueing ${n}`);
    const result = await doSome(n);
    console.log(result);
  };
}

looper();
Run Code Online (Sandbox Code Playgroud)

但是,正如您在日志中看到的,这种方法不会将下一个异步进程排队,直到上一个异步进程完成为止。这是不可取的,并且不符合您的用例。我们从前面的双循环方法中得到的是,所有异步进程都会立即执行,但随后我们对结果进行排序/排队,以便它们遵循我们预定义的顺序,而不是它们解析的顺序。

更新

关于Promise.all, async/await和排队的预期行为:

因此,如果您想避免使用async/ await,我认为您可以编写某种实用程序:

var doSome = function(i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`Completing ${i}`), 1000*(i%3))
  });
}

function handlePromiseQueue(queue) {
  let promise = queue.shift();
  promise.then((data) => {
    console.log(data)
    if (queue.length > 0) {
      handlePromiseQueue(queue);
    }
  })
}

var looper = function() {
  const nums = [1,2,3];
  const promises = []
  nums.forEach((n) => {
    console.log(`Queueing ${n}`);
    promises.push(doSome(n));
  });
  handlePromiseQueue(promises);
}

looper();
Run Code Online (Sandbox Code Playgroud)

然而,让我明确一点——如果用户Bergi的断言是正确的,并且每个异步承诺在解析后立即执行并不重要,只是在它们全部返回之前不会对它们采取行动,那么这可以 100% 简化为Promise.all

// This is probably what you want
var doSome = function(i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`Completing ${i}`), 1000*(i%3))
  });
}

function handlePromiseQueue(queue) {
  let promise = queue.shift();
  promise.then((data) => {
    console.log(data)
    if (queue.length > 0) {
      handlePromiseQueue(queue);
    }
  })
}

var looper = function() {
  const nums = [1,2,3];
  const promises = []
  nums.forEach((n) => {
    console.log(`Queueing ${n}`);
    promises.push(doSome(n));
  });
  Promise.all(promises).then(() => handlePromiseQueue(promises));
}

looper();
Run Code Online (Sandbox Code Playgroud)

最后,正如 Bergi 还指出的那样,我在这里玩得太快了,没有对这些不同的承诺进行任何设置——catch为了在示例中简洁,我省略了它们,但为了您的目的,您将需要对错误或不良请求进行正确的处理。

  • 你的第二个片段很危险,[不要创建多个承诺并依次“等待”它们,而是使用“Promise.all”](/sf/ask/3282250331/ -并发等待操作)! (2认同)