Как синхронизировать объекты промисов?

У меня есть объекты обещания, которые должны работать синхронно. Например, второе обещание не должно работать до того, как будет выполнено первое. Если первый отклоняет первый, его нужно выполнить снова.

Я реализовал несколько примеров. Этот работает хорошо. вызовите getVal, подождите 2000 мс, вернитесь, i++, снова вызовите getVal .....

 getVal() {
       return new Promise(function(resolve, reject) {
      setTimeout(function(){ resolve(19) }, 2000);
         });

     }

async promiseController(){

    for(var i =0;i<5;i++)
      {
        var _val = await this.getVal()
        console.log(_val+'prom');
      }
    }

Но мне нужно контролировать массив объектов-обещаний. Что я хочу сделать, так это то, что у меня есть данные, и я разделил их на 5 частей. После того, как первая часть обработана (например: отправлена ​​​​на сервер), я хочу обработать вторую часть, иначе мне придется снова обрабатывать первую часть.

Это реализация прототипа, которую я сделал

  getVal() {
   return new Promise(function(resolve, reject) {
  setTimeout(function(){ resolve(19) }, 2000);
     });

 }

async promiseController(){
  var proms=[]
  for(var i =0;i<5;i++)
    {
      proms.push(this.getVal())
    }

for(var i =0;i<5;i++)
  {
    var _val = await proms[i]
    console.log(_val+'prom');
  }
}

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


person Burak Karasoy    schedule 29.08.2016    source источник
comment
Я не могу сказать, пытаетесь ли вы выполнить Promise.all() или если вы хотите, чтобы ваши обещания больше напоминали Водопад.   -  person zero298    schedule 29.08.2016
comment
promise.all() у меня не работает. Он отклоняет строку после одного обещания вниз. В моей ситуации я должен заставить их все работать, иначе это бесполезно.   -  person Burak Karasoy    schedule 29.08.2016
comment
Каков ожидаемый результат, если рекурсивный вызов, который возвращает первое обещание, не разрешен?   -  person guest271314    schedule 29.08.2016
comment
Что мешает первому промису бесконечно повторяться?   -  person zero298    schedule 29.08.2016
comment
@guest71314 var i in for будет уменьшен, и первое обещание будет запущено снова.   -  person Burak Karasoy    schedule 29.08.2016
comment
@BurakKarasoy Ожидаемый результат для бесконечной рекурсии, если первое обещание отклонено?   -  person guest271314    schedule 29.08.2016
comment
@ guest71314 не бесконечно, но код должен быть упрямым (максимум 3-4 раза). Я отправляю данные, и они могут потеряться.   -  person Burak Karasoy    schedule 29.08.2016
comment
См. stackoverflow.com/questions/33718545/   -  person guest271314    schedule 29.08.2016
comment
@guest713114 guest713114 Я проверил ответ, но он кажется мне сложным, я не знаю, как его оптимизировать для работы с массивом обещаний.   -  person Burak Karasoy    schedule 29.08.2016
comment
@BurakKarasoy Используйте рекурсию, .then(), именованные функции, для вызова функции, пока каждый шаг не вернет разрешенное обещание   -  person guest271314    schedule 29.08.2016


Ответы (4)


async promiseController(){
  for(const value of array) {
    console.log((await this.getVal(value))+'prom');
  }
}

Не нужно слишком усложнять вещи. Просто вызовите await внутри цикла, и он будет ждать того, что вы хотите.

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

Если вы хотите игнорировать сбои, вы можете .catch(() => {}) выполнить обещание. Если вы хотите повторить попытку до сбоя, вы можете реорганизовать повторную попытку в функцию и использовать ее:

const retry = fn => (...args) => fn(...args).catch(retry(fn));
person Benjamin Gruenbaum    schedule 29.08.2016
comment
спасибо, но, как я уже упоминал в вопросе, у меня есть массив обещаний. Вызов getVal у меня не работает - person Burak Karasoy; 29.08.2016
comment
Это не имеет значения, вместо повторения от 0 до 4 повторяйте значения. Я обновил пример, чтобы показать, что вместо 0..4 - person Benjamin Gruenbaum; 29.08.2016
comment
вы имеете в виду (ожидание этого.getVal (значение)) или (ожидание значения) - person Burak Karasoy; 29.08.2016
comment
await this.getVal(value) - хранить массив значений и обрабатывать их в цикле. Если у вас есть промисы, вы уже потеряли возможность их синхронизировать. Вы синхронизируете функции. - person Benjamin Gruenbaum; 29.08.2016
comment
«Если у вас есть обещания, вы уже потеряли возможность их синхронизировать», это ключ. Спасибо, но я не понимаю, как использовать эту строку const retry = fn =› (...args) =› fn(. ..args).catch(повторить(fn)); - person Burak Karasoy; 29.08.2016
comment
Вам не нужно его использовать — это просто общая функция для повторной попытки выполнить невыполненное обещание. - person Benjamin Gruenbaum; 29.08.2016
comment
Спасибо за помощь. Если у вас есть любимая газета, сайт .. об экмаскрипте, обещаниях, доходах... предложите мне, я буду счастлив. - person Burak Karasoy; 29.08.2016

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

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

Таким образом, вы получите что-то вроде:

delay(time) {
    return new Promise(resolve => setTimeout(resolve, time));
}

async promiseController() {
    const factories = [];
    for (let i = 0; i < 5; ++i) {
        factories.push(() => this.getVal());
    }

    for (const f of factories) {
        // keep running this factory until it succeeds
        let success = false;
        while (!success) {
            try {
                const promise = f();
                const result = await f;
                success = true;
                console.log(`result = ${result}`);
            }
            catch (err) {
                console.log("promise failed.  retrying");
                await delay(100);
            }
        }
    }
}
person Brandon    schedule 29.08.2016
comment
Спасибо. Вы говорите: «Ваш первый пример делает это, не вызывая getVal() до тех пор, пока не завершится предыдущее обещание». Что не так со 2-м примером. В чем разница между await this.getVal() и await proms[i]. У обоих есть рефери обещания, не так ли? - person Burak Karasoy; 29.08.2016

Вы можете использовать рекурсию, именованную функцию, .then()

var arr = [Promise.resolve("a")
           , Promise.resolve("b")
           , Promise.resolve("c")];
var i = 0;
var res = [];

function foo() {
  // conditional resolved or rejected promise
  var n = String(new Date().getTime()).slice(-1);
  // if `n` < 5 reject `n` , else resolve `n`
  var curr = n < 5;
  return curr ? arr[i] : Promise.reject(["rejected", n])
}

var p = (function repeat() {
  var promise = foo();
  return promise
    .then(function(data) {
      console.log(data);
      res.push(data);
      ++i;
      if (i < arr.length) return repeat()
      // return `res` array when all promises complete
      else return res
    })
    .catch(function(err) {
      console.log(err);
      if (err[0] === "rejected") return repeat()
    })
}());

p.then(function(complete) {
  console.log("complete:", complete)
});

person guest271314    schedule 29.08.2016

Ладно, хорошо. Я считаю, что ради правильных целей функционального программирования следует избегать async и await. Я считаю, что обещаний вполне достаточно. Тем не менее, если вы хотите продолжить кодирование в императивном стиле C++, то async и await для вас.

У меня есть объекты обещания, которые должны работать синхронно. Например, второе обещание не должно работать до того, как будет выполнено первое. Если первый отклоняет первый, его нужно выполнить снова.

Позвольте мне кратко рассказать о коде ниже. У нас есть функция async(), которая принимает данные и обратный вызов (ошибка первого типа). Что касается демонстрационных целей, он попытается вызвать обратный вызов с данными в пределах 2000 мс, однако есть тайм-аут в 1000 мс. Итак, 50-50 он либо вызовет обратный вызов с данными, либо с ошибкой.

Таким образом, нам действительно нужно, чтобы он вернул нам обещание, поэтому я обещаю его с помощью promisify(), и он принимает функцию async() и возвращает мне функцию asyncPro(). Что на самом деле то же самое, что и async(), но вместо этого возвращает обещание. Таким образом, ожидается, что мы будем использовать наш обратный вызов на этапе then.

Затем идет функция tryNTimes(data,asyncFun,n = 5), которая принимает данные, обещанная асинхронная функция и целое число, определяющее количество попыток, прежде чем отклонить ее. Количество попыток по умолчанию равно 5, но вы можете установить любое значение, передав третий аргумент.

Что касается последней части, у нас есть flowControl(), который идеально связывает наши промисы с помощью Array.prototype.reduce().

Итак, теперь у нас есть все наши обещания, связанные одно за другим, и ни одно из них не подведет, пока мы не попытаемся 5 раз.

function promisify(fun){
  return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));
}

function async(data, callback){
  var dur = Math.floor(Math.random()*2000);
  setTimeout(_ => callback(false,data),dur);           // may resolve before timeout
  setTimeout(_ => callback("error at " + data),1000);  // timeout at 1 sec
}

function tryNTimes(data,asyncFun,n = 5){
  return new Promise((resolve,reject) => { n === 0 && reject("try out fail at 5 tries: " + data);
                                           asyncFun(data).then(v => resolve("resolved at countdown " + n + ": " + v))
                                                         .catch(e => resolve(tryNTimes(data,asyncFun,--n)));
                                         });
}

function flowControl(d,f,tc){
  return d.reduce((prom,chunk) => prom.then(v => { console.log(v);
                                                   return tryNTimes(chunk,f,tc);
                                                 }),Promise.resolve("initial dummy promise"));
}

var data = ["chunk_1", "chunk_2", "chunk_3", "chunk_4", "chunk_5"],
asyncPro = promisify(async);                           // now our async function returns a promise

flowControl(data,asyncPro).then(v => console.log(v))
                          .catch(e => console.log(e));

Если вы хотите, чтобы ошибки "5 раз пытались" появляться чаще, уменьшите значение времени ожидания в функции async().

person Redu    schedule 30.08.2016
comment
спасибо за этот подробный и пояснительный красивый пример. Я не понял один шаг. У обещания есть функция исполнителя, и эта функция выполняется сразу же, как она говорит (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/) Вы правильно сделали эту функцию асинхронной? Как может сделать эту функцию асинхронной, чтобы эта функция не вызывалась немедленно? - person Burak Karasoy; 30.08.2016
comment
@Бурак Карасой Не async. Да, когда вы создаете объект обещания, вы запускаете функцию-исполнитель, и в нашем примере исполнитель находится в функции promisfy. (resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)) Он будет синхронно выполняться и возвращать вам объект обещания, который будет ждать разрешения или отклонения нашей функции async. Если вы посмотрите, инструкция asyncPro = promisify(async); делает нашу функцию async аргументом fun в promisfy, и мы предоставляем обратные вызовы нашего исполнителя resolve и reject функции async - person Redu; 30.08.2016
comment
Promisify() не возвращает обещание, оно возвращает функцию, которая возвращает обещание, вот почему обещание запускается не сразу, а после вызова метода, верно? и должно ли имя метода быть асинхронным в этой ситуации? или Вы назвали его асинхронным только потому, что он имеет setTimeout(..) не можете ли вы назвать его _asyncFunc вместо асинхронного - person Burak Karasoy; 30.08.2016
comment
хорошо, я попробовал и получил ответ. Поскольку async является ключевым словом в ecmascript, это меня смутило. Я подумал, что это похоже на async-await.(ponyfoo.com/articles/understanding-javascript-async-await) - person Burak Karasoy; 30.08.2016
comment
@Burak Karasoy да, правда .. promisfy делает обычную асинхронную функцию обратного вызова для возврата объекта обещания. Он возвращает обещанную версию нашей функции async. Да, наша функция исполнителя встроена в asyncPro. Я просто назвал его async только потому, что в тот момент не мог найти лучшего имени. Это совершенно условно, ничего особенного. Вы можете назвать его kediCesur, если хотите. :) Извините, что запутал вас с названием. - person Redu; 30.08.2016
comment
Спасибо. kediCesur будет менее описательным, но и менее запутанным :) - person Burak Karasoy; 30.08.2016
comment
Давайте продолжим это обсуждение в чате. - person Redu; 30.08.2016