Bluebird Promise.any() досрочно отклонить?

Я использую библиотеку обещаний Bluebird во всех своих проектах Node.js. Для получения содержимого первого существующего файла из списка путей к файлам я успешно использую Promise.any следующим образом:

Promise.any([
   './foo/file1.yaml',
   './foo/file2.yaml',
   './foo/file3.yaml'
], function(filePath) {
    _readFile(filePath);
}),then(function(fileContent) {
    _console.log(fileContent);
});

Мой вопрос: как я могу выйти из цикла Promis.any раньше, если я получаю ошибку, отличную от «файл не найден», при чтении файла? Следующий код иллюстрирует мой вопрос:

Promise.any([
   './foo/file1.yaml',
   './foo/file2.yaml',
   './foo/file3.yaml'
], function(filePath) {
    _readFile(filePath)
    .catch(function(err) {
       var res = err;
       if (err.code == FILE_NOT_FOUND) {
          // continue the Promise.any loop
       } else {
          // leave the Promise.any loop with this error
          err = new Promise.EarlyBreak(err);
       }
       Promise.reject(err);
    });
}).then(function(fileContent) {
    _console.log(fileContent);
}, function(err) {
    // the first error different from FILE_NOT_FOUND
});

Может быть, Promise.any не та функция?


person k7sleeper    schedule 04.02.2016    source источник
comment
Я тоже использую bluebird во всех своих проектах, но ничего подобного не пробовал. Просто непроверенная идея, но можете ли вы установить переменную, такую ​​​​как var isErr, для которой вы установите значение true, если вы достигнете этого сценария раннего перерыва. Затем в других ваших промисах установите повторяющийся таймер, чтобы проверить статус этой переменной и отклонить, если это правда? Не сразу, но может быть довольно быстрым выходом.   -  person edencorbin    schedule 04.02.2016
comment
Э-э, согласно документации API Promise.any не принимает обратный вызов? А цикла все равно нет, их ждут параллельно? Я бы рекомендовал написать это вручную, используя reduce или рекурсию.   -  person Bergi    schedule 04.02.2016
comment
Да, ты прав. Promise.any не принимает обратный вызов. Я хочу, чтобы файлы проверялись последовательно, а следующий только в том случае, если предыдущий не существует. Во всех остальных случаях я хочу получить ошибку. В настоящее время я реализовал это с помощью Promise.each и глобального var, но я подумал, что есть лучшее решение. Я снова начну думать об использовании Promise.reduce.   -  person k7sleeper    schedule 04.02.2016
comment
На самом деле, я не вижу способа остановить reduce от вызова аккумулятора, если я успешно прочитал файл.   -  person k7sleeper    schedule 04.02.2016
comment
Вот еще два шаблона, которые сделают это. См. комментарии Джейка Арчибальда: github.com/w3c/ServiceWorker/issues/359#issuecomment. -49329009   -  person user5389726598465    schedule 12.03.2018


Ответы (1)


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

Однако, начиная с массива путей, цикл, который вы ищете, может быть выражен как выражение paths.reduce(...), которое строит цепочку .catch() следующим образом:

function getFirstGoodFileContent(paths) {
    paths.reduce(function(promise, path) {
        return promise.catch(function() {
            return _readFile(path);
        });
    }, Promise.reject()); // seed the chain with a rejected promise.
}

Catchain: кредит Берги

Построенная таким образом цепочка .catch будет переходить к следующей итерации в случае неудачи или переходить к концу цепочки в случае успеха. Это управление потоком является обратным тому, что происходит в более нормальной цепочке .then (засеянной выполненным обещанием).

Но это еще не все. Требуется дополнительное условие, а именно «досрочно выйти из цикла [Promise.any], если я получаю сообщение об ошибке, отличном от «файл не найден». Это очень просто встраивается в цепочку перехвата, отправляя все ошибки, кроме FILE_NOT_FOUND, по пути успеха, таким образом:

  • осуществление необходимого управления потоком (пропуская остальную часть цепочки), но
  • заканчивая состоянием ошибки, идущим по пути успеха - нежелательным, но исправимым.
function getFirstGoodFileContent(paths) {
    paths.reduce(function(promise, path) {
        return promise.catch(function() {
            return _readFile(path).catch(function(err) {
                if (err.code == FILE_NOT_FOUND) {
                    throw err; // Rethrow the error to continue down the catch chain, seeking a good path.
                } else {
                    return { isError: true, message: err.code }; // Skip the rest of the catch chain by returning a "surrogate success object".
                }
            });
        });
    }, Promise.reject()).then(function(fileContent) {
        // You will arrive here either because :
        // * a good path was found, or
        // * a non-FILE_NOT_FOUND error was encountered.
        // The error condition is detectable by testing `fileContent.isError`
        if (fileContent.isError) {
            throw new Error(fileContent.message); // convert surrogate success to failure.
        } else {
            return fileContent; // Yay, genuine success.
        }
    });
}

Итак, теперь вы можете позвонить:

getFirstGoodFileContent([
    './foo/file1.yaml',
    './foo/file2.yaml',
    './foo/file3.yaml'
]).then(function(fileContent) {
    _console.log(fileContent);
}, function(error) {
    // error will be due to :
    // * a non-FILE_NOT_FOUND error having occurred, or
    // * the final path having resulted in an error.
    console.log(error); 
});
person Roamer-1888    schedule 04.02.2016
comment
Вы доверяете мне в своем ответе - спасибо! - но вы ссылаетесь на мой комментарий выше или на другой мой ответ? - person Bergi; 04.02.2016
comment
@Bergi, еще один ответ - комментарий, я думаю - хороший год назад. Вы указали мне на простоту цепочки перехвата вместо некоторого замысловатого шаблона инверсии обещаний, который я разработал. Не могу найти, может удалил? - person Roamer-1888; 04.02.2016
comment
быстрый поиск в моих ответах вызвал это, которое кажется вероятным кандидатом :-) - person Bergi; 05.02.2016
comment
@ Берги, это так же хорошо, как и все, хотя и не то, что я помню. - person Roamer-1888; 05.02.2016
comment
Ах я вижу. Я должен обрабатывать фактическую ошибку так же, как и успешное извлечение содержимого файла. Хороший! Кроме того, файлы считываются только в том случае, если предыдущее чтение не удалось. После успешного чтения файла оставшиеся пути не создают обещание. Так что это очень легкий цикл по оставшимся путям. Спасибо! - person k7sleeper; 05.02.2016