一个接一个地解决承诺(即按顺序)?

发布于 2025-01-11 12:30:53 字数 832 浏览 0 评论 0原文

考虑以下以串行/顺序方式读取文件数组的代码。 readFiles 返回一个承诺,只有在按顺序读取所有文件后才会解决该承诺。

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  return new Promise((resolve, reject) => {
    var readSequential = function(index) {
      if (index >= files.length) {
        resolve();
      } else {
        readFile(files[index]).then(function() {
          readSequential(index + 1);
        }).catch(reject);
      }
    };

    readSequential(0); // Start with the first file!
  });
};

上面的代码有效,但我不喜欢必须进行递归才能使事情按顺序发生。有没有一种更简单的方法可以重写此代码,以便我不必使用奇怪的 readSequential 函数?

最初我尝试使用 Promise.all,但这导致所有 readFile 调用同时发生,这不是我想要的:

var readFiles = function(files) {
  return Promise.all(files.map(function(file) {
    return readFile(file);
  }));
};

Consider the following code that reads an array of files in a serial/sequential manner. readFiles returns a promise, which is resolved only once all files have been read in sequence.

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  return new Promise((resolve, reject) => {
    var readSequential = function(index) {
      if (index >= files.length) {
        resolve();
      } else {
        readFile(files[index]).then(function() {
          readSequential(index + 1);
        }).catch(reject);
      }
    };

    readSequential(0); // Start with the first file!
  });
};

The above code works, but I don't like having to do recursion for things to occur sequentially. Is there a simpler way that this code can be re-written so that I don't have to use my weird readSequential function?

Originally I tried to use Promise.all, but that caused all of the readFile calls to happen concurrently, which is not what I want:

var readFiles = function(files) {
  return Promise.all(files.map(function(file) {
    return readFile(file);
  }));
};

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

忆伤 2025-01-18 12:30:53

更新 2017:如果环境支持,我会使用异步函数:

async function readFiles(files) {
  for(const file of files) {
    await readFile(file);
  }
};

如果您愿意,您可以推迟读取文件,直到需要使用异步生成器(如果您的环境支持):

async function* readFiles(files) {
  for(const file of files) {
    yield await readFile(file);
  }
};

更新:再考虑一下 - 我可能会使用 for 循环来代替:

var readFiles = function(files) {
  var p = Promise.resolve(); // Q() in q

  files.forEach(file =>
      p = p.then(() => readFile(file)); 
  );
  return p;
};

或者更紧凑地使用 reduce:

var readFiles = function(files) {
  return files.reduce((p, file) => {
     return p.then(() => readFile(file));
  }, Promise.resolve()); // initial
};

在其他 Promise 库(如 when 和 Bluebird)中,您有用于此目的的实用方法。

例如,Bluebird 将是:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));

var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param

readAll.then(function(allFileContents){
    // do stuff to read files.
});

尽管现在确实没有理由使用 async wait。

Update 2017: I would use an async function if the environment supports it:

async function readFiles(files) {
  for(const file of files) {
    await readFile(file);
  }
};

If you'd like, you can defer reading the files until you need them using an async generator (if your environment supports it):

async function* readFiles(files) {
  for(const file of files) {
    yield await readFile(file);
  }
};

Update: In second thought - I might use a for loop instead:

var readFiles = function(files) {
  var p = Promise.resolve(); // Q() in q

  files.forEach(file =>
      p = p.then(() => readFile(file)); 
  );
  return p;
};

Or more compactly, with reduce:

var readFiles = function(files) {
  return files.reduce((p, file) => {
     return p.then(() => readFile(file));
  }, Promise.resolve()); // initial
};

In other promise libraries (like when and Bluebird) you have utility methods for this.

For example, Bluebird would be:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));

var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param

readAll.then(function(allFileContents){
    // do stuff to read files.
});

Although there is really no reason not to use async await today.

泪之魂 2025-01-18 12:30:53

这个问题很老了,但我们生活在 ES6 和函数式 JavaScript 的世界中,所以让我们看看如何改进。

因为 Promise 会立即执行,所以我们不能只创建一系列 Promise,它们都会并行触发。

相反,我们需要创建一个返回承诺的函数数组。然后每个函数将按顺序执行,然后启动内部的 Promise。

我们可以通过几种方法来解决这个问题,但我最喜欢的方法是使用reduce

reduce 与 Promise 结合使用会有点棘手,因此我将这一行代码分解为下面一些更小的、易于理解的部分。

该函数的本质是使用从 Promise.resolve([]) 初始值或包含空数组的 Promise 开始的 reduce

然后,这个 Promise 将作为 promise 传递到 reduce 方法中。这是将每个 Promise 按顺序链接在一起的关键。下一个要执行的 Promise 是 func,当 then 触发时,结果会被连接起来,然后返回该 Promise,执行 reduce 循环:下一个承诺函数。

一旦所有的 Promise 都执行完毕,返回的 Promise 将包含每个 Promise 的所有结果的数组。

ES6 示例(一行)

/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs =>
    funcs.reduce((promise, func) =>
        promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([]))

ES6 示例(分解)

// broken down to for easier understanding

const concat = list => Array.prototype.concat.bind(list)
const promiseConcat = f => x => f().then(concat(x))
const promiseReduce = (acc, x) => acc.then(promiseConcat(x))
/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs => funcs.reduce(promiseReduce, Promise.resolve([]))

用法:

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const funcs = urls.map(url => () => $.ajax(url))

// execute them serially
serial(funcs)
    .then(console.log.bind(console))

This question is old, but we live in a world of ES6 and functional JavaScript, so let's see how we can improve.

Because promises execute immediately, we can't just create an array of promises, they would all fire off in parallel.

Instead, we need to create an array of functions that returns a promise. Each function will then be executed sequentially, which then starts the promise inside.

We can solve this a few ways, but my favorite way is to use reduce.

It gets a little tricky using reduce in combination with promises, so I have broken down the one liner into some smaller digestible bites below.

The essence of this function is to use reduce starting with an initial value of Promise.resolve([]), or a promise containing an empty array.

This promise will then be passed into the reduce method as promise. This is the key to chaining each promise together sequentially. The next promise to execute is func and when the then fires, the results are concatenated and that promise is then returned, executing the reduce cycle with the next promise function.

Once all promises have executed, the returned promise will contain an array of all the results of each promise.

ES6 Example (one liner)

/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs =>
    funcs.reduce((promise, func) =>
        promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([]))

ES6 Example (broken down)

// broken down to for easier understanding

const concat = list => Array.prototype.concat.bind(list)
const promiseConcat = f => x => f().then(concat(x))
const promiseReduce = (acc, x) => acc.then(promiseConcat(x))
/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs => funcs.reduce(promiseReduce, Promise.resolve([]))

Usage:

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const funcs = urls.map(url => () => $.ajax(url))

// execute them serially
serial(funcs)
    .then(console.log.bind(console))
℡Ms空城旧梦 2025-01-18 12:30:53

这是我更喜欢串行运行任务的方式。

function runSerial() {
    var that = this;
    // task1 is a function that returns a promise (and immediately starts executing)
    // task2 is a function that returns a promise (and immediately starts executing)
    return Promise.resolve()
        .then(function() {
            return that.task1();
        })
        .then(function() {
            return that.task2();
        })
        .then(function() {
            console.log(" ---- done ----");
        });
}

任务较多的情况怎么办?比如,10?

function runSerial(tasks) {
  var result = Promise.resolve();
  tasks.forEach(task => {
    result = result.then(() => task());
  });
  return result;
}

Here is how I prefer to run tasks in series.

function runSerial() {
    var that = this;
    // task1 is a function that returns a promise (and immediately starts executing)
    // task2 is a function that returns a promise (and immediately starts executing)
    return Promise.resolve()
        .then(function() {
            return that.task1();
        })
        .then(function() {
            return that.task2();
        })
        .then(function() {
            console.log(" ---- done ----");
        });
}

What about cases with more tasks? Like, 10?

function runSerial(tasks) {
  var result = Promise.resolve();
  tasks.forEach(task => {
    result = result.then(() => task());
  });
  return result;
}
长亭外,古道边 2025-01-18 12:30:53

在 ES6 中简单地做到这一点:

function(files) {
  // Create a new empty promise (don't do that with real people ;)
  var sequence = Promise.resolve();

  // Loop over each file, and add on a promise to the
  // end of the 'sequence' promise.
  files.forEach(file => {

    // Chain one computation onto the sequence
    sequence = 
      sequence
        .then(() => performComputation(file))
        .then(result => doSomething(result)); 
        // Resolves for each file, one at a time.

  })

  // This will resolve after the entire chain is resolved
  return sequence;
}

To do this simply in ES6:

function(files) {
  // Create a new empty promise (don't do that with real people ;)
  var sequence = Promise.resolve();

  // Loop over each file, and add on a promise to the
  // end of the 'sequence' promise.
  files.forEach(file => {

    // Chain one computation onto the sequence
    sequence = 
      sequence
        .then(() => performComputation(file))
        .then(result => doSomething(result)); 
        // Resolves for each file, one at a time.

  })

  // This will resolve after the entire chain is resolved
  return sequence;
}
错々过的事 2025-01-18 12:30:53

加法示例

const addTwo = async () => 2;

const addThree = async (inValue) => new Promise((resolve) => setTimeout(resolve(inValue + 3), 2000));

const addFour = (inValue) => new Promise((res) => setTimeout(res(inValue + 4), 1000)); 

const addFive = async (inValue) => inValue + 5;

// Function which handles promises from above
async function sequenceAddition() {
  let sum = await [addTwo, addThree, addFour, addFive].reduce(
    (promise, currPromise) => promise.then((val) => currPromise(val)),
    Promise.resolve()
  );
  console.log('sum:', sum); // 2 + 3 + 4 + 5 =  14
}

// Run function. See console for result.
sequenceAddition();

使用 reduce()

function sequence(tasks, fn) {
    return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}

UPDATE

的一般语法items- Promise 是一个现成的 NPM 包,可以做同样的事情。

Addition example

const addTwo = async () => 2;

const addThree = async (inValue) => new Promise((resolve) => setTimeout(resolve(inValue + 3), 2000));

const addFour = (inValue) => new Promise((res) => setTimeout(res(inValue + 4), 1000)); 

const addFive = async (inValue) => inValue + 5;

// Function which handles promises from above
async function sequenceAddition() {
  let sum = await [addTwo, addThree, addFour, addFive].reduce(
    (promise, currPromise) => promise.then((val) => currPromise(val)),
    Promise.resolve()
  );
  console.log('sum:', sum); // 2 + 3 + 4 + 5 =  14
}

// Run function. See console for result.
sequenceAddition();

General syntax to use reduce()

function sequence(tasks, fn) {
    return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}

UPDATE

items-promise is a ready to use NPM package doing the same.

抚笙 2025-01-18 12:30:53

我必须运行很多顺序任务,并使用这些答案来创建一个函数来处理任何顺序任务...

function one_by_one(objects_array, iterator, callback) {
    var start_promise = objects_array.reduce(function (prom, object) {
        return prom.then(function () {
            return iterator(object);
        });
    }, Promise.resolve()); // initial
    if(callback){
        start_promise.then(callback);
    }else{
        return start_promise;
    }
}

该函数需要 2 个参数 + 1 个可选参数。第一个参数是我们将要处理的数组。第二个参数是任务本身,一个返回承诺的函数,只有当这个承诺解决时才会启动下一个任务。第三个参数是所有任务完成后运行的回调。如果没有传递回调,那么该函数将返回它创建的 Promise,以便我们可以处理结束。

这是一个用法示例:

var filenames = ['1.jpg','2.jpg','3.jpg'];
var resize_task = function(filename){
    //return promise of async resizing with filename
};
one_by_one(filenames,resize_task );

希望它可以节省某人一些时间......

I've had to run a lot of sequential tasks and used these answers to forge a function that would take care of handling any sequential task...

function one_by_one(objects_array, iterator, callback) {
    var start_promise = objects_array.reduce(function (prom, object) {
        return prom.then(function () {
            return iterator(object);
        });
    }, Promise.resolve()); // initial
    if(callback){
        start_promise.then(callback);
    }else{
        return start_promise;
    }
}

The function takes 2 arguments + 1 optional. First argument is the array on which we will be working. The second argument is the task itself, a function that returns a promise, the next task will be started only when this promise resolves. The third argument is a callback to run when all tasks have been done. If no callback is passed, then the function returns the promise it created so we can handle the end.

Here's an example of usage:

var filenames = ['1.jpg','2.jpg','3.jpg'];
var resize_task = function(filename){
    //return promise of async resizing with filename
};
one_by_one(filenames,resize_task );

Hope it saves someone some time...

窝囊感情。 2025-01-18 12:30:53

/Await(如果支持 ES7)

function downloadFile(fileUrl) { ... } // This function return a Promise

async function main()
{
  var filesList = [...];

  for (const file of filesList) {
    await downloadFile(file);
  }
}

(必须使用 for 循环,而不是 forEach,因为 async/await 在 forEach 循环中运行时会出现问题)

使用Async /等待(使用 Promise)

function downloadFile(fileUrl) { ... } // This function return a Promise

function downloadRecursion(filesList, index)
{
  index = index || 0;
  if (index < filesList.length)
  {
    downloadFile(filesList[index]).then(function()
    {
      index++;
      downloadRecursion(filesList, index); // self invocation - recursion!
    });
  }
  else
  {
    return Promise.resolve();
  }
}

function main()
{
  var filesList = [...];
  downloadRecursion(filesList);
}

With Async/Await (if you have the support of ES7)

function downloadFile(fileUrl) { ... } // This function return a Promise

async function main()
{
  var filesList = [...];

  for (const file of filesList) {
    await downloadFile(file);
  }
}

(you must use for loop, and not forEach because async/await has problems running in forEach loop)

Without Async/Await (using Promise)

function downloadFile(fileUrl) { ... } // This function return a Promise

function downloadRecursion(filesList, index)
{
  index = index || 0;
  if (index < filesList.length)
  {
    downloadFile(filesList[index]).then(function()
    {
      index++;
      downloadRecursion(filesList, index); // self invocation - recursion!
    });
  }
  else
  {
    return Promise.resolve();
  }
}

function main()
{
  var filesList = [...];
  downloadRecursion(filesList);
}
北凤男飞 2025-01-18 12:30:53

首先,您需要了解 Promise 在创建时执行。
例如,如果您有这样的代码:

["a","b","c"].map(x => returnsPromise(x))

您需要将其更改为:

["a","b","c"].map(x => () => returnsPromise(x))

然后我们需要按顺序链接 Promise:

["a", "b", "c"].map(x => () => returnsPromise(x))
    .reduce(
        (before, after) => before.then(_ => after()),
        Promise.resolve()
    )

执行 after(),将确保仅当 Promise 被创建(并执行)时时间到了。

First, you need to understand that a promise is executed at the time of creation.
So for example if you have a code:

["a","b","c"].map(x => returnsPromise(x))

You need to change it to:

["a","b","c"].map(x => () => returnsPromise(x))

Then we need to sequentially chain promises:

["a", "b", "c"].map(x => () => returnsPromise(x))
    .reduce(
        (before, after) => before.then(_ => after()),
        Promise.resolve()
    )

executing after(), will make sure that promise is created (and executed) only when its time comes.

国粹 2025-01-18 12:30:53

我的首选解决方案:

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

它与此处发布的其他解决方案没有根本不同,但是:

  • 将函数应用于系列中的项目
  • 解析为结果数组
  • 不需要异步/等待(支持仍然相当有限,大约在 2017 年) )
  • 使用箭头函数;漂亮简洁的

示例用法:

const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));

// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);

在合理的当前 Chrome (v59) 和 NodeJS (v8.1.2) 上进行测试。

My preferred solution:

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

It's not fundamentally different from others published here but:

  • Applies the function to items in series
  • Resolves to an array of results
  • Doesn't require async/await (support is still quite limited, circa 2017)
  • Uses arrow functions; nice and concise

Example usage:

const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));

// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);

Tested on reasonable current Chrome (v59) and NodeJS (v8.1.2).

神回复 2025-01-18 12:30:53

使用 ES2016 的 async/await(也许还有 ES2018 的某些功能),这可以简化为这种形式:

function readFile(file) {
  ... // Returns a promise.
}

async function readFiles(files) {
  for (file in files) {
     await readFile(file)
  }
}

我还没有看到另一个答案表达了这种简单性。 OP 表示不需要并行执行 readFile。然而,对于这样的 IO,在保持循环执行同步的同时,不阻塞单个文件的读取确实很有意义(在读取所有文件之前,您不想执行下一步)。由于我刚刚了解到这一点并且对此感到有点兴奋,因此我将分享 readFile 的并行异步执行与 readFiles 的整体同步执行的方法。

async function readFiles(files) {
  await Promise.all(files.map(readFile))
}

这难道不是一件美事吗?

With async/await of ES2016 (and maybe some features of ES2018), this can be reduced to this form:

function readFile(file) {
  ... // Returns a promise.
}

async function readFiles(files) {
  for (file in files) {
     await readFile(file)
  }
}

I haven't seen another answer express that simplicity. The OP said parallel execution of readFile was not desired. However, with IO like this it really makes sense to not be blocking on a single file read, while keeping the loop execution synchronous (you don't want to do the next step until all files have been read). Since I just learned about this and am a bit excited about it, I'll share that approach of parallel asynchronous execution of readFile with overall synchronous execution of readFiles.

async function readFiles(files) {
  await Promise.all(files.map(readFile))
}

Isn't that a thing of beauty?

口干舌燥 2025-01-18 12:30:53

我能想到的最好的解决方案是使用 bluebird Promise。您只需执行 Promise.resolve(files).each(fs.readFileAsync); 即可保证按顺序依次解决 Promise。

Nicest solution that I was able to figure out was with bluebird promises. You can just do Promise.resolve(files).each(fs.readFileAsync); which guarantees that promises are resolved sequentially in order.

幻梦 2025-01-18 12:30:53

这是上面另一个答案的轻微变化。使用原生 Promises:

function inSequence(tasks) {
    return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}

解释

如果您有这些任务[t1, t2, t3],那么上面相当于Promise.resolve().then(t1 ).then(t2).then(t3)。这是减少的行为。

如何使用

首先您需要构建一个任务列表!任务是一个不接受参数的函数。如果您需要将参数传递给函数,请使用 bind 或其他方法来创建任务。例如:

var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)

This is a slight variation of another answer above. Using native Promises:

function inSequence(tasks) {
    return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}

Explanation

If you have these tasks [t1, t2, t3], then the above is equivalent to Promise.resolve().then(t1).then(t2).then(t3). It's the behavior of reduce.

How to use

First You need to construct a list of tasks! A task is a function that accepts no argument. If you need to pass arguments to your function, then use bind or other methods to create a task. For example:

var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)
末が日狂欢 2025-01-18 12:30:53

大多数答案并不单独包含所有承诺的结果,因此,如果有人正在寻找这种特定行为,这是使用递归的可能解决方案。

它遵循 Promise.all 的风格:

  • 返回 .then() 回调中的结果数组。

  • 如果某个 Promise 失败,则立即在 .catch() 回调中返回。

const promiseEach = (arrayOfTasks) => {
let results = []
return new Promise((resolve, reject) => {
const resolveNext = (arrayOfTasks) => {
// If all tasks are already resolved, return the final array of results
if (arrayOfTasks.length === 0) return resolve(results)

// Extract first promise and solve it
const first = arrayOfTasks.shift()

first().then((res) => {
results.push(res)
resolveNext(arrayOfTasks)
}).catch((err) => {
reject(err)
})
}
resolveNext(arrayOfTasks)
})
}

// Lets try it

Most of the answers dont include the results of ALL promises individually, so in case someone is looking for this particular behaviour, this is a possible solution using recursion.

It follows the style of Promise.all:

  • Returns the array of results in the .then() callback.

  • If some promise fails, its returned immediately in the .catch() callback.

const promiseEach = (arrayOfTasks) => {
  let results = []
  return new Promise((resolve, reject) => {
    const resolveNext = (arrayOfTasks) => {
      // If all tasks are already resolved, return the final array of results
      if (arrayOfTasks.length === 0) return resolve(results)

      // Extract first promise and solve it
      const first = arrayOfTasks.shift()

      first().then((res) => {
        results.push(res)
        resolveNext(arrayOfTasks)
      }).catch((err) => {
        reject(err)
      })
    }
    resolveNext(arrayOfTasks)
  })
}

// Lets try it ????

const promise = (time, shouldThrowError) => new Promise((resolve, reject) => {
  const timeInMs = time * 1000
  setTimeout(()=>{
    console.log(`Waited ${time} secs`)
    if (shouldThrowError) reject(new Error('Promise failed'))
    resolve(time)
  }, timeInMs)
})

const tasks = [() => promise(1), () => promise(2)]

promiseEach(tasks)
  .then((res) => {
    console.log(res) // [1, 2]
  })
  // Oops some promise failed
  .catch((error) => {
    console.log(error)
  })

Note about the tasks array declaration:

In this case is not possible to use the following notation like Promise.all would use:

const tasks = [promise(1), promise(2)]

And we have to use:

const tasks = [() => promise(1), () => promise(2)]

The reason is that JavaScript starts executing the promise immediatelly after its declared. If we use methods like Promise.all, it just checks that the state of all of them is fulfilled or rejected, but doesnt start the exection itself. Using () => promise() we stop the execution until its called.

只是在用心讲痛 2025-01-18 12:30:53

我发现自己多次回到这个问题,但答案并没有完全满足我的需要,因此将其放在这里供任何也需要这个问题的人使用。

下面的代码按顺序执行 Promise(一个接一个),每一轮都包含多个调用:

async function sequence(list, cb) {
  const result = [];
  await list.reduce(async (promise, item) => promise
    .then(() => cb(item))
    .then((res) => result.push(res)
  ), Promise.resolve());
  return result;
}

Showcase:

<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script type="text/babel">

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function readFile(url, index) {
  console.log('Running index: ', index);

  // First action
  const firstTime = await axios.get(url);
  console.log('First API response: ', firstTime.data.activity);
  
  // Second action
  await sleep(1000);
  
  // Third action
  const secondTime = await axios.get(url);
  console.log('Second API response: ', secondTime.data.activity);

  // Fourth action
  await sleep(1000);

  return secondTime.data;
}

async function sequence(urls, fn) {
  const result = [];
  await urls.reduce(async (promise, url, index) => promise.then(() => fn(url, index)).then((res) => result.push(res)), Promise.resolve());
  return result;
}

const urls = [
  'https://www.boredapi.com/api/activity',
  'https://www.boredapi.com/api/activity',
  'https://www.boredapi.com/api/activity',
];

(async function init() {
  const result = await sequence(urls, readFile);
  console.log('result', result);
})()

</script>

I find myself coming back to this question many times and the answers aren't exactly giving me what I need, so putting this here for anyone that needs this too.

The code below does sequential promises execution (one after another), and each round consists of multiple callings:

async function sequence(list, cb) {
  const result = [];
  await list.reduce(async (promise, item) => promise
    .then(() => cb(item))
    .then((res) => result.push(res)
  ), Promise.resolve());
  return result;
}

Showcase:

<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script type="text/babel">

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function readFile(url, index) {
  console.log('Running index: ', index);

  // First action
  const firstTime = await axios.get(url);
  console.log('First API response: ', firstTime.data.activity);
  
  // Second action
  await sleep(1000);
  
  // Third action
  const secondTime = await axios.get(url);
  console.log('Second API response: ', secondTime.data.activity);

  // Fourth action
  await sleep(1000);

  return secondTime.data;
}

async function sequence(urls, fn) {
  const result = [];
  await urls.reduce(async (promise, url, index) => promise.then(() => fn(url, index)).then((res) => result.push(res)), Promise.resolve());
  return result;
}

const urls = [
  'https://www.boredapi.com/api/activity',
  'https://www.boredapi.com/api/activity',
  'https://www.boredapi.com/api/activity',
];

(async function init() {
  const result = await sequence(urls, readFile);
  console.log('result', result);
})()

</script>

如此安好 2025-01-18 12:30:53

我在 Promise 对象上创建了这个简单的方法:

创建 Promise.sequence 方法并将其添加到 Promise 对象

Promise.sequence = function (chain) {
    var results = [];
    var entries = chain;
    if (entries.entries) entries = entries.entries();
    return new Promise(function (yes, no) {
        var next = function () {
            var entry = entries.next();
            if(entry.done) yes(results);
            else {
                results.push(entry.value[1]().then(next, function() { no(results); } ));
            }
        };
        next();
    });
};

用法:

var todo = [];

todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);

// Invoking them
Promise.sequence(todo)
    .then(function(results) {}, function(results) {});

Promise 对象的这种扩展最好的一点是,它与 Promise 的风格一致。 Promise.all 和 Promise.sequence 的调用方式相同,但语义不同。

警告

顺序运行 Promise 通常不是使用 Promise 的好方法。通常最好使用 Promise.all,并让浏览器尽可能快地运行代码。然而,它有实际的用例 - 例如使用 JavaScript 编写移动应用程序时。

I created this simple method on the Promise object:

Create and add a Promise.sequence method to the Promise object

Promise.sequence = function (chain) {
    var results = [];
    var entries = chain;
    if (entries.entries) entries = entries.entries();
    return new Promise(function (yes, no) {
        var next = function () {
            var entry = entries.next();
            if(entry.done) yes(results);
            else {
                results.push(entry.value[1]().then(next, function() { no(results); } ));
            }
        };
        next();
    });
};

Usage:

var todo = [];

todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);

// Invoking them
Promise.sequence(todo)
    .then(function(results) {}, function(results) {});

The best thing about this extension to the Promise object, is that it is consistent with the style of promises. Promise.all and Promise.sequence is invoked the same way, but have different semantics.

Caution

Sequential running of promises is not usually a very good way to use promises. It's usually better to use Promise.all, and let the browser run the code as fast as possible. However, there are real use cases for it - for example when writing a mobile app using javascript.

番薯 2025-01-18 12:30:53

我的答案基于https://stackoverflow.com/a/31070150/7542429

Promise.series = function series(arrayOfPromises) {
    var results = [];
    return arrayOfPromises.reduce(function(seriesPromise, promise) {
      return seriesPromise.then(function() {
        return promise
        .then(function(result) {
          results.push(result);
        });
      });
    }, Promise.resolve())
    .then(function() {
      return results;
    });
  };

此解决方案将结果作为数组返回,如 Promise.all()。

用法:

Promise.series([array of promises])
.then(function(results) { 
  // do stuff with results here
});

My answer based on https://stackoverflow.com/a/31070150/7542429.

Promise.series = function series(arrayOfPromises) {
    var results = [];
    return arrayOfPromises.reduce(function(seriesPromise, promise) {
      return seriesPromise.then(function() {
        return promise
        .then(function(result) {
          results.push(result);
        });
      });
    }, Promise.resolve())
    .then(function() {
      return results;
    });
  };

This solution returns the results as an array like Promise.all().

Usage:

Promise.series([array of promises])
.then(function(results) { 
  // do stuff with results here
});
韬韬不绝 2025-01-18 12:30:53

使用 Array.prototype.reduce,并记住将你的 Promise 包装在一个函数中,否则它们已经在运行了!

// array of Promise providers

const providers = [
  function(){
     return Promise.resolve(1);
  },
  function(){
     return Promise.resolve(2);
  },
  function(){
     return Promise.resolve(3);
  }
]


const inSeries = function(providers){

  const seed = Promise.resolve(null); 

  return providers.reduce(function(a,b){
      return a.then(b);
  }, seed);
};

又好又容易...
您应该能够重复使用相同的种子来提高性能等。

在使用reduce时,防止空数组或只有1个元素的数组非常重要,因此这种技术是您最好的选择:

   const providers = [
      function(v){
         return Promise.resolve(v+1);
      },
      function(v){
         return Promise.resolve(v+2);
      },
      function(v){
         return Promise.resolve(v+3);
      }
    ]

    const inSeries = function(providers, initialVal){

        if(providers.length < 1){
            return Promise.resolve(null)
        }

        return providers.reduce((a,b) => a.then(b), providers.shift()(initialVal));
    };

然后称它为:

inSeries(providers, 1).then(v => {
   console.log(v);  // 7
});

Use Array.prototype.reduce, and remember to wrap your promises in a function otherwise they will already be running!

// array of Promise providers

const providers = [
  function(){
     return Promise.resolve(1);
  },
  function(){
     return Promise.resolve(2);
  },
  function(){
     return Promise.resolve(3);
  }
]


const inSeries = function(providers){

  const seed = Promise.resolve(null); 

  return providers.reduce(function(a,b){
      return a.then(b);
  }, seed);
};

nice and easy...
you should be able to re-use the same seed for performance, etc.

It's important to guard against empty arrays or arrays with only 1 element when using reduce, so this technique is your best bet:

   const providers = [
      function(v){
         return Promise.resolve(v+1);
      },
      function(v){
         return Promise.resolve(v+2);
      },
      function(v){
         return Promise.resolve(v+3);
      }
    ]

    const inSeries = function(providers, initialVal){

        if(providers.length < 1){
            return Promise.resolve(null)
        }

        return providers.reduce((a,b) => a.then(b), providers.shift()(initialVal));
    };

and then call it like:

inSeries(providers, 1).then(v => {
   console.log(v);  // 7
});
别闹i 2025-01-18 12:30:53

使用现代 ES:

const series = async (tasks) => {
  const results = [];

  for (const task of tasks) {
    const result = await task();

    results.push(result);
  }

  return results;
};

//...

const tasks = files.map(file => {
  return () => readFile(file);
});

const readFiles = await series(tasks);

请注意,promise 在 JS 中是“热门”的,因此为了将每个任务从开始推迟到前一个任务完成,我们将其包装在 lambda 中。

Using modern ES:

const series = async (tasks) => {
  const results = [];

  for (const task of tasks) {
    const result = await task();

    results.push(result);
  }

  return results;
};

//...

const tasks = files.map(file => {
  return () => readFile(file);
});

const readFiles = await series(tasks);

Note that promises are "hot" in JS so to defer each task from starting until the previous task completes, we wrap it in a lambda.

束缚m 2025-01-18 12:30:53

您可以使用此函数来获取 PromiseFactories 列表:

function executeSequentially(promiseFactories) {
    var result = Promise.resolve();
    promiseFactories.forEach(function (promiseFactory) {
        result = result.then(promiseFactory);
    });
    return result;
}

Promise Factory 只是返回 Promise 的简单函数:

function myPromiseFactory() {
    return somethingThatCreatesAPromise();
}

它之所以有效,是因为 Promise 工厂在被要求之前不会创建 Promise。它的工作方式与 then 函数相同 - 事实上,它是同一件事!

你根本不想对一系列承诺进行操作。根据 Promise 规范,一旦创建 Promise,它就会开始执行。所以你真正想要的是一系列的 Promise 工厂......

如果你想了解更多关于 Promise 的信息,你应该检查这个链接:
https://pouchdb.com/2015/ 05/18/we-have-a-problem-with-promises.html

You can use this function that gets promiseFactories List:

function executeSequentially(promiseFactories) {
    var result = Promise.resolve();
    promiseFactories.forEach(function (promiseFactory) {
        result = result.then(promiseFactory);
    });
    return result;
}

Promise Factory is just simple function that returns a Promise:

function myPromiseFactory() {
    return somethingThatCreatesAPromise();
}

It works because a promise factory doesn't create the promise until it's asked to. It works the same way as a then function – in fact, it's the same thing!

You don't want to operate over an array of promises at all. Per the Promise spec, as soon as a promise is created, it begins executing. So what you really want is an array of promise factories...

If you want to learn more on Promises, you should check this link:
https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

勿忘初心 2025-01-18 12:30:53

如果你愿意,你可以使用reduce来做出顺序承诺,例如:

[2,3,4,5,6,7,8,9].reduce((promises, page) => {
    return promises.then((page) => {
        console.log(page);
        return Promise.resolve(page+1);
    });
  }, Promise.resolve(1));

它总是按顺序工作。

If you want you can use reduce to make a sequential promise, for example:

[2,3,4,5,6,7,8,9].reduce((promises, page) => {
    return promises.then((page) => {
        console.log(page);
        return Promise.resolve(page+1);
    });
  }, Promise.resolve(1));

it'll always works in sequential.

看透却不说透 2025-01-18 12:30:53

我真的很喜欢@joelnet的答案,但对我来说,这种编码风格有点难以消化,所以我花了几天时间试图弄清楚如何以更易读的方式表达相同的解决方案,这是我的采取,只是使用不同的语法和一些注释。

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const functions = urls.map((url) => {
  // For every url we return a new function
  return () => {
    return new Promise((resolve) => {
      // random wait in milliseconds
      const randomWait = parseInt((Math.random() * 1000),10)
      console.log('waiting to resolve in ms', randomWait)
      setTimeout(()=>resolve({randomWait, url}),randomWait)
    })
  }
})


const promiseReduce = (acc, next) => {
  // we wait for the accumulator to resolve it's promise
  return acc.then((accResult) => {
    // and then we return a new promise that will become
    // the new value for the accumulator
    return next().then((nextResult) => {
      // that eventually will resolve to a new array containing
      // the value of the two promises
      return accResult.concat(nextResult)
    })
  })
};
// the accumulator will always be a promise that resolves to an array
const accumulator = Promise.resolve([])

// we call reduce with the reduce function and the accumulator initial value
functions.reduce(promiseReduce, accumulator)
  .then((result) => {
    // let's display the final value here
    console.log('=== The final result ===')
    console.log(result)
  })

I really liked @joelnet's answer, but to me, that style of coding is a little bit tough to digest, so I spent a couple of days trying to figure out how I would express the same solution in a more readable manner and this is my take, just with a different syntax and some comments.

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const functions = urls.map((url) => {
  // For every url we return a new function
  return () => {
    return new Promise((resolve) => {
      // random wait in milliseconds
      const randomWait = parseInt((Math.random() * 1000),10)
      console.log('waiting to resolve in ms', randomWait)
      setTimeout(()=>resolve({randomWait, url}),randomWait)
    })
  }
})


const promiseReduce = (acc, next) => {
  // we wait for the accumulator to resolve it's promise
  return acc.then((accResult) => {
    // and then we return a new promise that will become
    // the new value for the accumulator
    return next().then((nextResult) => {
      // that eventually will resolve to a new array containing
      // the value of the two promises
      return accResult.concat(nextResult)
    })
  })
};
// the accumulator will always be a promise that resolves to an array
const accumulator = Promise.resolve([])

// we call reduce with the reduce function and the accumulator initial value
functions.reduce(promiseReduce, accumulator)
  .then((result) => {
    // let's display the final value here
    console.log('=== The final result ===')
    console.log(result)
  })
野味少女 2025-01-18 12:30:53

正如 Bergi 注意到的,我认为最好、最明确的解决方案是使用 BlueBird.each,代码如下:

const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);

As Bergi noticed, I think the best and clear solution is use BlueBird.each, code below:

const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);
笑咖 2025-01-18 12:30:53

我使用以下代码来扩展 Promise 对象。它处理承诺的拒绝并返回结果数组

代码

/*
    Runs tasks in sequence and resolves a promise upon finish

    tasks: an array of functions that return a promise upon call.
    parameters: an array of arrays corresponding to the parameters to be passed on each function call.
    context: Object to use as context to call each function. (The 'this' keyword that may be used inside the function definition)
*/
Promise.sequence = function(tasks, parameters = [], context = null) {
    return new Promise((resolve, reject)=>{

        var nextTask = tasks.splice(0,1)[0].apply(context, parameters[0]); //Dequeue and call the first task
        var output = new Array(tasks.length + 1);
        var errorFlag = false;

        tasks.forEach((task, index) => {
            nextTask = nextTask.then(r => {
                output[index] = r;
                return task.apply(context, parameters[index+1]);
            }, e=>{
                output[index] = e;
                errorFlag = true;
                return task.apply(context, parameters[index+1]);
            });
        });

        // Last task
        nextTask.then(r=>{
            output[output.length - 1] = r;
            if (errorFlag) reject(output); else resolve(output);
        })
        .catch(e=>{
            output[output.length - 1] = e;
            reject(output);
        });
    });
};

示例

function functionThatReturnsAPromise(n) {
    return new Promise((resolve, reject)=>{
        //Emulating real life delays, like a web request
        setTimeout(()=>{
            resolve(n);
        }, 1000);
    });
}

var arrayOfArguments = [['a'],['b'],['c'],['d']];
var arrayOfFunctions = (new Array(4)).fill(functionThatReturnsAPromise);


Promise.sequence(arrayOfFunctions, arrayOfArguments)
.then(console.log)
.catch(console.error);

I use the following code to extend the Promise object. It handles rejection of the promises and returns an array of results

Code

/*
    Runs tasks in sequence and resolves a promise upon finish

    tasks: an array of functions that return a promise upon call.
    parameters: an array of arrays corresponding to the parameters to be passed on each function call.
    context: Object to use as context to call each function. (The 'this' keyword that may be used inside the function definition)
*/
Promise.sequence = function(tasks, parameters = [], context = null) {
    return new Promise((resolve, reject)=>{

        var nextTask = tasks.splice(0,1)[0].apply(context, parameters[0]); //Dequeue and call the first task
        var output = new Array(tasks.length + 1);
        var errorFlag = false;

        tasks.forEach((task, index) => {
            nextTask = nextTask.then(r => {
                output[index] = r;
                return task.apply(context, parameters[index+1]);
            }, e=>{
                output[index] = e;
                errorFlag = true;
                return task.apply(context, parameters[index+1]);
            });
        });

        // Last task
        nextTask.then(r=>{
            output[output.length - 1] = r;
            if (errorFlag) reject(output); else resolve(output);
        })
        .catch(e=>{
            output[output.length - 1] = e;
            reject(output);
        });
    });
};

Example

function functionThatReturnsAPromise(n) {
    return new Promise((resolve, reject)=>{
        //Emulating real life delays, like a web request
        setTimeout(()=>{
            resolve(n);
        }, 1000);
    });
}

var arrayOfArguments = [['a'],['b'],['c'],['d']];
var arrayOfFunctions = (new Array(4)).fill(functionThatReturnsAPromise);


Promise.sequence(arrayOfFunctions, arrayOfArguments)
.then(console.log)
.catch(console.error);
A君 2025-01-18 12:30:53

您的方法还不错,但它确实有两个问题:它会吞掉错误,并且它采用了显式承诺构造反模式。

您可以解决这两个问题,并使代码更简洁,同时仍然采用相同的总体策略:

var Q = require("q");

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  var readSequential = function(index) {
    if (index < files.length) {
      return readFile(files[index]).then(function() {
        return readSequential(index + 1);
      });
    }
  };

  // using Promise.resolve() here in case files.length is 0
  return Promise.resolve(readSequential(0)); // Start!
};

Your approach is not bad, but it does have two issues: it swallows errors and it employs the Explicit Promise Construction Antipattern.

You can solve both of these issues, and make the code cleaner, while still employing the same general strategy:

var Q = require("q");

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  var readSequential = function(index) {
    if (index < files.length) {
      return readFile(files[index]).then(function() {
        return readSequential(index + 1);
      });
    }
  };

  // using Promise.resolve() here in case files.length is 0
  return Promise.resolve(readSequential(0)); // Start!
};
泛滥成性 2025-01-18 12:30:53

这是我在各种项目中使用的sequential实现:

const file = [file1, file2, file3];
const fileContents = sequentially(readFile, files);

// somewhere else in the code:

export const sequentially = async <T, P>(
  toPromise: (element: T) => Promise<P>,
  elements: T[]
): Promise<P[]> => {
  const results: P[] = [];
  await elements.reduce(async (sequence, element) => {
    await sequence;
    results.push(await toPromise(element));
  }, Promise.resolve());

  return results;
};

This is my sequentially implementation that I use in various projects:

const file = [file1, file2, file3];
const fileContents = sequentially(readFile, files);

// somewhere else in the code:

export const sequentially = async <T, P>(
  toPromise: (element: T) => Promise<P>,
  elements: T[]
): Promise<P[]> => {
  const results: P[] = [];
  await elements.reduce(async (sequence, element) => {
    await sequence;
    results.push(await toPromise(element));
  }, Promise.resolve());

  return results;
};

你是年少的欢喜 2025-01-18 12:30:53

这是我使用 RxJS 的 Angular/TypeScript 方法:

  1. 给定一个 URL 字符串数组,使用 from 函数将其转换为 Observable。
  2. 使用 pipe 来包装 Ajax 请求、立即响应逻辑、任何所需的延迟和错误处理。
  3. pipe内部,使用concatMap来序列化请求。否则,使用 Javascript forEachmap 会同时发出请求。
  4. 使用 RxJS ajax 进行调用,并在每次调用返回后添加任何所需的延迟。

工作示例: https://stackblitz.com/edit/rxjs-bnrkix?file =index.ts

代码如下所示(我留下了一些额外内容,以便您可以选择保留或丢弃哪些内容):

import { ajax } from 'rxjs/ajax';
import { catchError, concatMap, delay, from, of, map, Observable } from 'rxjs';

const urls = [
  'https://randomuser.me/api/',
  'https://randomuser.me/api/',
  'https://randomuser.me/api/',
];
const delayAfterCall = 500;

from(urls)
  .pipe(
    concatMap((url: string) => {
      return ajax.getJSON(url).pipe(
        map((response) => {
          console.log('Done! Received:', response);
          return response;
        }),
        catchError((error) => {
          console.error('Error: ', error);
          return of(error);
        }),
        delay(delayAfterCall)
      );
    })
  )
  .subscribe((response) => {
    console.log('received email:', response.results[0].email);
  });

Here is my Angular/TypeScript approach, using RxJS:

  1. Given an array of URL strings, convert it into an Observable using the from function.
  2. Use pipe to wrap the Ajax request, immediate response logic, any desired delay, and error handling.
  3. Inside of the pipe, use concatMap to serialize the requests. Otherwise, using Javascript forEach or map would make the requests at the same time.
  4. Use RxJS ajax to make the call, and also to add any desired delay after each call returns.

Working example: https://stackblitz.com/edit/rxjs-bnrkix?file=index.ts

The code looks like this (I left in some extras so you can choose what to keep or discard):

import { ajax } from 'rxjs/ajax';
import { catchError, concatMap, delay, from, of, map, Observable } from 'rxjs';

const urls = [
  'https://randomuser.me/api/',
  'https://randomuser.me/api/',
  'https://randomuser.me/api/',
];
const delayAfterCall = 500;

from(urls)
  .pipe(
    concatMap((url: string) => {
      return ajax.getJSON(url).pipe(
        map((response) => {
          console.log('Done! Received:', response);
          return response;
        }),
        catchError((error) => {
          console.error('Error: ', error);
          return of(error);
        }),
        delay(delayAfterCall)
      );
    })
  )
  .subscribe((response) => {
    console.log('received email:', response.results[0].email);
  });
一身软味 2025-01-18 12:30:53

根据问题标题“一个接一个地解决承诺(即按顺序)?”,我们可能会理解,OP 更感兴趣的是结算时承诺的顺序处理,而不是顺序调用本身。 >。

提供这个答案是

  • 为了证明顺序调用对于顺序处理响应来说不是必需的。
  • 向该页面的访问者公开可行的替代模式 - 包括 OP,如果他在一年多后仍然感兴趣的话。
  • 尽管OP声称他不想同时拨打电话,这可能确实是这样,但同样可能是基于标题所暗示的顺序处理响应的愿望的假设。

如果确实不需要并发调用,请参阅 Benjamin Gruenbaum 的答案,其中全面涵盖了顺序调用(等)。

但是,如果您对允许并发调用然后顺序处理响应的模式感兴趣(为了提高性能),那么请继续阅读。

人们很容易认为您必须使用 Promise.all(arr.map(fn)).then(fn) (正如我多次所做的那样)或 Promise lib 的精美糖(尤其是 Bluebird 的),然而(归功于这篇文章arr.map(fn).reduce(fn) 模式可以完成这项工作,其优点是:

  • 与任何 Promise 库一起工作 - 甚至是 jQuery 的预兼容版本 - 仅 .then使用 ()
  • 提供跳过错误或错误停止的灵活性,无论您想要使用单行模式。

这是为 Q 编写的。

var readFiles = function(files) {
    return files.map(readFile) //Make calls in parallel.
    .reduce(function(sequence, filePromise) {
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//skip-over-error. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

注意:只有一个片段 Q() 特定于 Q。对于 jQuery,您需要确保 readFile() 返回一个 jQuery 承诺。使用 A+ 库,外部承诺将被同化。

这里的关键是归约的 sequence 承诺,它对 readFile处理进行排序> 承诺但不是他们的创造。

一旦您理解了这一点,当您意识到 .map() 阶段实际上并不需要时,可能会有点令人兴奋!整个工作,并行调用加上正确顺序的串行处理,可以单独使用reduce()来实现,再加上进一步灵活性的额外优势:

  • 通过简单地从并行异步调用转换为串行异步调用移动一行——在开发过程中可能有用。

在这里,再次为Q

var readFiles = function(files) {
    return files.reduce(function(sequence, f) {
        var filePromise = readFile(f);//Make calls in parallel. To call sequentially, move this line down one.
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//Skip over any errors. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

这就是基本模式。如果您还想向调用者传递数据(例如文件或它们的某些转换),您将需要一个温和的变体。

On the basis of the question's title, "Resolve promises one after another (i.e. in sequence)?", we might understand that the OP is more interested in the sequential handling of promises on settlement than sequential calls per se.

This answer is offered :

  • to demonstrate that sequential calls are not necessary for sequential handling of responses.
  • to expose viable alternative patterns to this page's visitors - including the OP if he is still interested over a year later.
  • despite the OP's assertion that he does not want to make calls concurrently, which may genuinely be the case but equally may be an assumption based on the desire for sequential handling of responses as the title implies.

If concurrent calls are genuinely not wanted then see Benjamin Gruenbaum's answer which covers sequential calls (etc) comprehensively.

If however, you are interested (for improved performance) in patterns which allow concurrent calls followed by sequential handling of responses, then please read on.

It's tempting to think you have to use Promise.all(arr.map(fn)).then(fn) (as I have done many times) or a Promise lib's fancy sugar (notably Bluebird's), however (with credit to this article) an arr.map(fn).reduce(fn) pattern will do the job, with the advantages that it :

  • works with any promise lib - even pre-compliant versions of jQuery - only .then() is used.
  • affords the flexibility to skip-over-error or stop-on-error, whichever you want with a one line mod.

Here it is, written for Q.

var readFiles = function(files) {
    return files.map(readFile) //Make calls in parallel.
    .reduce(function(sequence, filePromise) {
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//skip-over-error. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

Note: only that one fragment, Q(), is specific to Q. For jQuery you need to ensure that readFile() returns a jQuery promise. With A+ libs, foreign promises will be assimilated.

The key here is the reduction's sequence promise, which sequences the handling of the readFile promises but not their creation.

And once you have absorbed that, it's maybe slightly mind-blowing when you realise that the .map() stage isn't actually necessary! The whole job, parallel calls plus serial handling in the correct order, can be achieved with reduce() alone, plus the added advantage of further flexibility to :

  • convert from parallel async calls to serial async calls by simply moving one line - potentially useful during development.

Here it is, for Q again.

var readFiles = function(files) {
    return files.reduce(function(sequence, f) {
        var filePromise = readFile(f);//Make calls in parallel. To call sequentially, move this line down one.
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//Skip over any errors. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

That's the basic pattern. If you wanted also to deliver data (eg the files or some transform of them) to the caller, you would need a mild variant.

飘落散花 2025-01-18 12:30:53

如果其他人在执行 CRUD 操作时需要一种有保证的严格顺序方式来解决 Promise,您也可以使用以下代码作为基础。

只要您在调用每个函数之前添加“return”,描述一个 Promise,并使用此示例作为基础,下一个 .then() 函数调用将始终在上一个函数完成后开始:

getRidOfOlderShoutsPromise = () => {
    return readShoutsPromise('BEFORE')
    .then(() => {
        return deleteOlderShoutsPromise();
    })
    .then(() => {
        return readShoutsPromise('AFTER')
    })
    .catch(err => console.log(err.message));
}

deleteOlderShoutsPromise = () => {
    return new Promise ( (resolve, reject) => {
        console.log("in deleteOlderShouts");
        let d = new Date();
        let TwoMinuteAgo = d - 1000 * 90 ;
        All_Shouts.deleteMany({ dateTime: {$lt: TwoMinuteAgo}}, function(err) {
            if (err) reject();
            console.log("DELETED OLDs at "+d);
            resolve();        
        });
    });
}

readShoutsPromise = (tex) => {
    return new Promise( (resolve, reject) => {
        console.log("in readShoutsPromise -"+tex);
        All_Shouts
        .find({})
        .sort([['dateTime', 'ascending']])
        .exec(function (err, data){
            if (err) reject();
            let d = new Date();
            console.log("shouts "+tex+" delete PROMISE = "+data.length +"; date ="+d);
            resolve(data);
        });    
    });
}

If someone else needs a guaranteed way of STRICTLY sequential way of resolving Promises when performing CRUD operations you also can use the following code as a basis.

As long as you add 'return' before calling each function, describing a Promise, and use this example as a basis the next .then() function call will CONSISTENTLY start after the completion of the previous one:

getRidOfOlderShoutsPromise = () => {
    return readShoutsPromise('BEFORE')
    .then(() => {
        return deleteOlderShoutsPromise();
    })
    .then(() => {
        return readShoutsPromise('AFTER')
    })
    .catch(err => console.log(err.message));
}

deleteOlderShoutsPromise = () => {
    return new Promise ( (resolve, reject) => {
        console.log("in deleteOlderShouts");
        let d = new Date();
        let TwoMinuteAgo = d - 1000 * 90 ;
        All_Shouts.deleteMany({ dateTime: {$lt: TwoMinuteAgo}}, function(err) {
            if (err) reject();
            console.log("DELETED OLDs at "+d);
            resolve();        
        });
    });
}

readShoutsPromise = (tex) => {
    return new Promise( (resolve, reject) => {
        console.log("in readShoutsPromise -"+tex);
        All_Shouts
        .find({})
        .sort([['dateTime', 'ascending']])
        .exec(function (err, data){
            if (err) reject();
            let d = new Date();
            console.log("shouts "+tex+" delete PROMISE = "+data.length +"; date ="+d);
            resolve(data);
        });    
    });
}
临风闻羌笛 2025-01-18 12:30:53

数组推送和弹出方法可用于 Promise 序列。当您需要更多数据时,您还可以推动新的承诺。这是代码,我将在 React Infinite 加载器中使用来加载页面序列。

var promises = [Promise.resolve()];

function methodThatReturnsAPromise(page) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(`Resolve-${page}! ${new Date()} `);
			resolve();
		}, 1000);
	});
}

function pushPromise(page) {
	promises.push(promises.pop().then(function () {
		return methodThatReturnsAPromise(page)
	}));
}

pushPromise(1);
pushPromise(2);
pushPromise(3);

Array push and pop method can be used for sequence of promises. You can also push new promises when you need additional data. This is the code, I will use in React Infinite loader to load sequence of pages.

var promises = [Promise.resolve()];

function methodThatReturnsAPromise(page) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(`Resolve-${page}! ${new Date()} `);
			resolve();
		}, 1000);
	});
}

function pushPromise(page) {
	promises.push(promises.pop().then(function () {
		return methodThatReturnsAPromise(page)
	}));
}

pushPromise(1);
pushPromise(2);
pushPromise(3);

南风几经秋 2025-01-18 12:30:53

Nodejs 中有 promise-sequence

const promiseSequence = require('promise-sequence');
return promiseSequence(arr.map(el => () => doPromise(el)));

There's promise-sequence in nodejs.

const promiseSequence = require('promise-sequence');
return promiseSequence(arr.map(el => () => doPromise(el)));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文