Как разорвать цепочку обещаний?

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

У меня есть следующее:

    var runTests = function (chain, resolutionTest) {
        return chain.then(function (result) {
            if (result)
                return result; // Early return if the previous tests were successful.  This is where I want to prevent other attempts.
            const attempt = tryOpenStream(resolutionTest).then(streamToDom);
            return attempt;
        });
    }

    // from someplace else
    numTests = resolutionTests.length;
    return resolutionTests.reduce(runTests, Promise.resolve()); // start reduce with an empty promise 

Проблема, с которой я столкнулся, заключается в том, что я звоню tryOpenStream много раз даже после того, как захватил result.

Варианты, которые я рассматриваю:

  • Поднимите какой-нибудь глобальный флаг, который просто предотвращает дальнейшее выполнение внутри цепочки. Фу, потому что цепочка все еще продолжается, она просто опустела.
  • throw new Error(result) вместо return result. Это разорвет цепочку (я думаю...), но это неправильное использование Error и будет легко неправильно понято другим разработчиком.

Как мне разорвать эту цепочку на return result;?

ОБНОВЛЕНИЕ 1

Я пытаюсь сделать следующее:

   var makeTest = function (runMoreTests, resolutionTest) {
        return function runTest() {
            return tryOpenStream(resolutionTest).then(streamToDom).then(function (result) {
                if (result)
                    return result;
                else
                    return runMoreTests();
            });
        };
    }

    return resolutionTestBuilder.buildTests().then(function (resolutionTests) {
        numTests = resolutionTests.length;
        return resolutionTests.reduceRight(makeTest, function () { Promise.reject("No resolutions succeeded.") })();
    });

Однако вызовы runTest не вызываются. Это немного новый синтаксис для меня, поэтому я изучу некоторые и обновлю любые выводы.

ОБНОВЛЕНИЕ 2

Мне не хватало (), чтобы вызвать reduceRight. Хотя теперь я вижу, что reject вызывается даже с успехом... хотя, когда я прохожу, этот отказ не вызывается. Как будто к тому времени, когда я получаю результат, все звенья в цепочке уже задействованы.


person SB2055    schedule 20.06.2017    source источник
comment
Вы также видели эти несколько вопросы?   -  person Bergi    schedule 20.06.2017
comment
Re Upd 2: Не знаю, почему вы получаете отклоненное обещание. Вы уверены, что один из вызовов streamToDom возвращает верный результат, а runMoreTests не всегда вызывается? Можете ли вы выполнить некоторую отладку или опубликовать вывод console.log(result), введенного в этот обратный вызов?   -  person Bergi    schedule 20.06.2017
comment
@Bergi Я вижу, как return runMoreTests() вызывается несколько раз, затем return result, затем return runMoreTests() еще много раз, пока не будет вызвано отклонение.   -  person SB2055    schedule 20.06.2017
comment
Хм, после return result больше ничего запускать не надо. Вы используете правильную реализацию обещания, которая никогда не вызывает then обратных вызовов несколько раз, верно? Я не могу представить себе другую причину.   -  person Bergi    schedule 20.06.2017


Ответы (2)


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

Вместо этого используйте рекурсию, как в ответе @IsiahMeadows, или правую складку:

var makeTest = function (runMoreTests, resolutionTest) {
    return function runTest(result) {
        if (result)
            return result;
        return tryOpenStream(resolutionTest).then(streamToDom).then(runMoreTests);
    };
}

return Promise.resolve(resolutionTests.reduceRight(makeTest, x => x)(undefined));

или лучше написать как

var makeTest = function (runMoreTests, resolutionTest) {
    return function runTest() {
        return tryOpenStream(resolutionTest).then(streamToDom).then(result => {
            if (result)
                return result;
            else
                return runMoreTests();
        });
    };
}

return resolutionTests.reduceRight(makeTest, () => Promise.reject("nothing succeeded"))();
person Bergi    schedule 20.06.2017
comment
Только что обновил свой вопрос своей попыткой того, что вы предлагаете - спасибо за помощь. - person SB2055; 20.06.2017
comment
Я должен признать, что правильная складка довольно эзотерична и трудна для понимания, вы можете использовать более простой (и, возможно, более производительный) рекурсивный подход. - person Bergi; 20.06.2017
comment
Да, я играю с этим, хотя я не совсем понимаю его, рекурсия пугает меня еще больше. Моя первоначальная реализация была построена на рекурсии, и отладка была кошмаром. Я еще раз обновил свой вопрос с результатами, которые я вижу - он работает, хотя я вижу некоторые интересные результаты (отказ всегда вызывается) - person SB2055; 20.06.2017
comment
Сначала попытайтесь понять рекурсивное решение, мой подход основан на нем, но использует складку для обхода массива. Не уверен, почему это не сработает, но я тоже не проверял, так что, возможно, у него есть недостаток. - person Bergi; 20.06.2017

Попробуйте использовать это:

function runTests(resolutionTest) {
    return tryOpenStream(resolutionTest).then(streamToDom)
}

// from someplace else
function loop(tests, i) {
    if (i === tests.length) return undefined
    return runTests(tests[i]).then(function (result) {
        if (result) return result
        return loop(tests, i + 1)
    })
}
return loop(resolutionTests, 0)

Хотя мне интересно, почему вы не можете использовать исключение, чтобы обозначить, что ваш tryOpenStream потерпел неудачу. Это на самом деле упростило бы ваш код.

person Isiah Meadows    schedule 20.06.2017