Разрешать обещания одно за другим (т.е. по порядку)?

Рассмотрим следующий код, который читает массив файлов в последовательном / последовательном порядке. 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!

  });
};

Код выше кода работает, но мне не нравится делать рекурсию для вещей, которые происходят последовательно. Есть ли более простой способ переписать этот код, чтобы мне не пришлось использовать мой странный readSequential функционировать?

Изначально я пытался использовать Promise.all, но это вызвало все readFile вызовы происходят одновременно, чего я не хочу:

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

39 ответов

Решение

Обновление 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;
};

Или более компактно, с уменьшением:

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

В других библиотеках обещаний (например, когда и 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, ожидают сегодня.

Этот вопрос старый, но мы живем в мире ES6 и функционального JavaScript, поэтому давайте посмотрим, как мы можем улучшить.

Поскольку обещания выполняются немедленно, мы не можем просто создать массив обещаний, они все будут выполняться параллельно.

Вместо этого нам нужно создать массив функций, которые возвращают обещание. Каждая функция будет выполняться последовательно, что затем запускает обещание внутри.

Мы можем решить это несколькими способами, но мой любимый способ - использовать reduce,

Это становится немного сложнее, используя reduce в сочетании с обещаниями, поэтому я разбил один вкладыш на несколько меньших усваиваемых кусочков ниже.

Суть этой функции заключается в использовании reduce начиная с начального значения Promise.resolve([])или обещание, содержащее пустой массив.

Это обещание будет затем передано в reduce метод как promise, Это ключ к последовательному соединению каждого обещания. Следующее обещание выполнить func и когда then пожары, результаты объединяются, и это обещание затем возвращается, выполняя reduce цикл со следующей функцией обещания.

После выполнения всех обещаний возвращенное обещание будет содержать массив всех результатов каждого обещания.

Пример 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))

Вот как я предпочитаю выполнять задачи в серии.

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;
}

Чтобы сделать это просто в 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(function(file) {

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

    })

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

Простая утилита для стандартного обещания Node.js:

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

ОБНОВИТЬ

items-обещание - готовый пакет NPM, делающий то же самое.

Мне пришлось выполнить много последовательных задач и использовать эти ответы, чтобы создать функцию, которая позаботилась бы о выполнении любой последовательной задачи...

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 необязательный. Первый аргумент - это массив, над которым мы будем работать. Второй аргумент - это сама задача, функция, которая возвращает обещание, следующая задача будет запущена только после разрешения этого обещания. Третий аргумент - это обратный вызов, который запускается, когда все задачи выполнены. Если обратный вызов не передан, то функция возвращает обещание, которое она создала, чтобы мы могли обработать конец.

Вот пример использования:

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 );

Надеюсь, это сэкономит кому-то время...

Я не понимаю, почему люди предлагают такие сложные решения. Вот более простая логика

С Async/Await - лучшее решение, если у вас есть поддержка ES7

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

async function main()
{
  var filesList = [...];
  for (var i = 0; i <= filesList.length; i++)
  {
    await downloadFile(filesList[i]);
  }
}

Без асинхронного / ожидающего

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

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

function main()
{
  var filesList = [...];
  downloadFile(filesList, 0);
}

Во-первых, вам нужно понять, что обещание выполняется в момент создания.
Так, например, если у вас есть код:

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

Вам нужно изменить его на:

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

Затем нам нужно последовательно связать обещания:

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

выполнение after(), обеспечит создание (и выполнение) обещания только тогда, когда придет его время.

Мое предпочтительное решение:

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

Это не принципиально отличается от других, опубликованных здесь, но:

  • Применяет функцию к элементам в серии
  • Разрешает массив результатов
  • Не требует async / await (поддержка все еще довольно ограничена, около 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).

Самое хорошее решение, которое я смог выяснить, было с bluebird обещания. Вы можете просто сделать Promise.resolve(files).each(fs.readFileAsync); что гарантирует, что обещания решаются последовательно в порядке.

С помощью async/await ES2016 (и, возможно, некоторых функций ES2018) это можно свести к следующей форме:

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

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

Я не видел другого ответа, выражающего эту простоту. OP сказал, что параллельное выполнение readFile нежелательно. Однако при таком вводе-выводе действительно имеет смысл не блокировать чтение одного файла, сохраняя при этом выполнение цикла синхронным (вы не хотите выполнять следующий шаг, пока не будут прочитаны все файлы). Поскольку я только что узнал об этом и немного взволнован, я поделюсь этим подходом параллельного асинхронного выполнения readFile с общим синхронным выполнением readFiles.

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

Разве это не красота?

Это небольшое изменение другого ответа выше. Использование нативных обещаний:

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(...)

Большинство ответов не включают результаты ВСЕХ обещаний по отдельности, поэтому, если кто-то ищет такое поведение, это возможное решение с использованием рекурсии.

Он следует стилю Promise.all:

  • Возвращает массив результатов в .then() Перезвоните.

  • Если какое-то обещание не выполняется, оно немедленно возвращается в .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 

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)
  })

Примечание о tasksобъявление массива:

В этом случае нельзя использовать следующие обозначения вроде Promise.all будет использовать:

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

И мы должны использовать:

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

Причина в том, что JavaScript начинает выполнение обещания сразу после его объявления. Если мы используем такие методы, какPromise.all, он просто проверяет, что все они находятся в состоянии fulfilled или rejected, но не запускает само выполнение. С помощью() => promise() мы останавливаем выполнение до его вызова.

Я создал этот простой метод на объекте 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.all и Promise.sequence вызываются одинаково, но имеют разную семантику.

предосторожность

Последовательное выполнение обещаний обычно не очень хороший способ использовать обещания. Обычно лучше использовать Promise.all и позволить браузеру запускать код как можно быстрее. Тем не менее, есть реальные варианты использования для этого - например, при написании мобильного приложения с использованием JavaScript.

Мой ответ основан на /questions/26411667/razreshat-obeschaniya-odno-za-drugim-te-po-poryadku/26411681#26411681.

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
});

Использование Array.prototype.reduceи не забудьте обернуть свои обещания в функцию, иначе они уже будут запущены!

// array of Promise providers

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


const seed = Promise.resolve(null);

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

красиво и просто... вы должны иметь возможность повторно использовать одно и то же семя для производительности и т. д.

Я возвращаюсь к этому вопросу много раз, и ответы не совсем дают мне то, что мне нужно, поэтому размещаю это здесь для всех, кому это тоже нужно.

Код ниже выполняет последовательное выполнение промисов (одно за другим), и каждый раунд состоит из нескольких вызовов:

      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;
}

Витрина:

Используя современные ЭС:

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

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

    results.push(result);
  }

  return results;
};

//...

const readFiles = await series(files.map(readFile));

Вы можете использовать эту функцию, которая получает список 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();
}

Это работает, потому что фабрика обещаний не создает обещание, пока его не попросят. Он работает так же, как функция then - фактически, это то же самое!

Вы вообще не хотите оперировать множеством обещаний. Согласно спецификации Promise, как только обещание создано, оно начинает выполняться. Так что вы действительно хотите, это множество фабрик обещаний...

Если вы хотите узнать больше об обещаниях, вы должны проверить эту ссылку: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

Как заметил Берги, я думаю, что лучшее и понятное решение - использовать BlueBird.each, код ниже:

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

Если вы хотите, вы можете использовать сокращение, чтобы сделать последовательное обещание, например:

[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));

это всегда будет работать последовательно.

Мне очень понравился ответ @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)
  })

Вот мой подход Angular/TypeScript с использованием RxJS:

  1. Получив массив строк URL, преобразуйте его в Observable с помощьюfromфункция.
  2. Используйте для переноса запроса Ajax, логики немедленного ответа, любой желаемой задержки и обработки ошибок.
  3. Внутриpipe, использоватьconcatMapдля сериализации запросов. В противном случае, используя JavascriptforEachилиmapбудет делать запросы в то же время.
  4. Используйте RxJSajaxдля совершения вызова, а также для добавления любой желаемой задержки после каждого возврата вызова.

Рабочий пример: 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);
  });

Ваш подход неплох, но у него есть две проблемы: он глотает ошибки и использует Антипаттерн Explicit Promise Construction.

Вы можете решить обе эти проблемы и сделать код чище, используя при этом одну и ту же общую стратегию:

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!
};

Я использую следующий код для расширения объекта 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);

Это мой sequentially реализация, которую использую в различных проектах:

      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;
};

Есть пакет npm , который отлично с этим справляется:

      const Promise_serial = require('promise-serial');

 const promises =
    Array(15).fill()
    .map((_, i) =>
        () => new Promise(resolve => {
            console.log('promise '+i+' start');
            setTimeout(
                () => {
                    console.log('promise '+i+' end');
                    resolve('output-'+i);
                },
                500
            );
        })
    );


console.log('### Run promises in sequence')

Promise_serial(promises)

Выходы:

      promise 0 start
promise 0 end
promise 1 start
promise 1 end
promise 2 start
promise 2 end
promise 3 start
promise 3 end
promise 4 start
promise 4 end
promise 5 start
promise 5 end
promise 6 start
promise 6 end
promise 7 start
promise 7 end

... etc

Вы также можете группировать или распараллеливать их.

См .: Promise Serialhttps://www.npmjs.com/package/promise-serial

Исходя из заголовка вопроса "Разрешать обещания одно за другим (т.е. по порядку)?", Мы можем понять, что ФП больше заинтересован в последовательной обработке обещаний по расчету, чем в последовательных вызовах как таковых.

Этот ответ предлагается:

  • продемонстрировать, что последовательные вызовы не нужны для последовательной обработки ответов.
  • представить жизнеспособные альтернативные шаблоны посетителям этой страницы - в том числе ОП, если он все еще заинтересован более года спустя.
  • несмотря на утверждение ОП о том, что он не хочет совершать вызовы одновременно, что может действительно иметь место, но в равной степени может быть допущением, основанным на желании последовательной обработки ответов, как следует из названия.

Если одновременные вызовы действительно нежелательны, см. Ответ Бенджамина Грюнбаума, в котором подробно рассматриваются последовательные вызовы (и т. Д.).

Однако, если вы заинтересованы (для повышения производительности) в шаблонах, которые допускают одновременные вызовы с последующей последовательной обработкой ответов, тогда, пожалуйста, продолжайте читать.

Заманчиво думать, что вы должны использовать Promise.all(arr.map(fn)).then(fn) (как я делал много раз) или необычный сахар в Promise lib (особенно Bluebird's), однако (с благодарностью этой статье) arr.map(fn).reduce(fn) шаблон будет делать работу, с преимуществами, которые он:

  • работает с любым обещанием lib - даже с предварительно совместимыми версиями 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+ libs, иностранные обещания будут ассимилированы.

Ключевым моментом здесь является сокращение 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.
    });
};

Это основная модель. Если вы хотите также доставить данные (например, файлы или их преобразование) вызывающей стороне, вам понадобится мягкий вариант.

Есть <tcode id="135053"></tcode> в nodejs.

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

Чистая версия с использованиемlodash.flow

      // Example promises
const promise1 = async () => {
  await new Promise((resolve) => {
    setTimeout(() => resolve(), 1000);
  });
  return [{
    name: 'John',
    age: 30
  }];
};

const promise2 = async (result) => {
  await new Promise((resolve) => {
    setTimeout(() => resolve(), 2000);
  });
  return [...result, {
    name: 'Jane',
    age: 25
  }];
};

const promise3 = async (result) => {
  await new Promise((resolve) => {
    setTimeout(() => resolve(), 1500);
  });
  return [...result, {
    name: 'Bob',
    age: 40
  }];
};

// Array of promises
const promises = [promise1, promise2, promise3];

// Execute promises sequentially and combine results
const executePromisesSequentially = _.flow(
  ...promises.map((promise) => async (result) => promise(await result))
);

// Invoke the sequential execution
(async () => {
  try {
    const finalResult = await executePromisesSequentially();
    console.log('Final Result:', finalResult);
  } catch (error) {
    console.error('Error:', error);
  }
})();

Выход:

      Final Result: 
[
  {name: 'John', age: 30}
  {name: 'Jane', age: 25}
  {name: 'Bob', age: 40}
]
Другие вопросы по тегам