Выполнить (не выполнять) обещание другим обещанием

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

Вот пример использования, который я имею в виду:

var picker = pickFile();

picker.then(  // Wait for the user to pick a file.
    function(downloadProgress) {
        // The user picked a file. The file may not be available just yet (e.g.,
        // if it has to be downloaded over the network) but we can already ask
        // the user some more questions while the file is being obtained in the
        // background.

        ...do some more user interaction...

        return downloadProgress;
    }
).then( // Wait for the download (if any) to complete.
    function(file) {
        // Do something with the file.
    }
)

Функция pickFile отображает средство выбора файлов, в котором пользователь может выбрать файл либо со своего жесткого диска, либо с URL-адреса. Он возвращает обещание picker, которое выполняется, как только пользователь выбрал файл. На этом этапе нам все еще может потребоваться загрузить выбранный файл по сети. Следовательно, я не могу выполнить picker с выбранным файлом в качестве значения разрешения. Вместо этого picker следует выполнить с другим обещанием, downloadProgress, которое, в свою очередь, в конечном итоге будет выполнено с выбранным файлом.

Для полноты, вот имитация реализации функции pickFile:

function pickFile() {
    ...display the file picker...

    var resolveP1 = null;

    var p1 = new Promise(
        function(resolve, reject) {
            resolveP1 = resolve;
        }
    );

    // Mock code to pretend the user picked a file
    window.setTimeout(function() {
        var p2 = Promise.resolve('thefile');
        resolveP1(p2);  // <--- PROBLEM: I actually want to *fulfill* p1 with p2
    }, 3000);

    return p1;
}

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

Я мог бы обойти эту проблему, построив оболочку вокруг p2, т.е. заменив строку

        resolveP1(p2);  // <--- PROBLEM: I actually want to *fulfill* p1 with p2

из второго примера кода

        resolveP1({promise: p2});

Затем в первом примере кода мне пришлось бы заменить строку

        return downloadProgress;

by

        return downloadProgress.promise;

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

Буду признателен за любые предложения.


person robamler    schedule 23.08.2015    source источник
comment
Обещания разворачиваются автоматически, единственный способ избежать этого - не разрешить с помощью другого обещания, как вы упомянули. Есть ли обратная сторона в разрешении с помощью объекта result изначально с именем файла или другими данными, а затем с наличием там .file функции или свойства, которое разрешается при загрузке?   -  person loganfsmyth    schedule 23.08.2015
comment
К сожалению, это невозможно с обещаниями ES6. Боюсь, это никогда не будет исправлено, так как обходной путь слишком прост.   -  person Bergi    schedule 23.08.2015
comment
Является ли ваш образец демонстрационным сценарием или его использует ваша команда? Если это ваш случай пользователя, вы слишком усложняете ситуацию. Единственная асинхронная операция здесь - это загрузка, поэтому следует указать файл, а все остальное сделать в синхронном коде. После того, как весь синхронный код будет выполнен, пусть функция выполнения обработает файл.   -  person Amit    schedule 24.08.2015
comment
@Amit: Может быть, я называл это сборщиком файлов и вводил в заблуждение. Это не модальный диалог открытия файла, связанный с <input type="file">. Вместо этого pickFile отображает <form> с некоторыми полями ввода для выбора файла (локально или по URL-адресу) и кнопкой продолжения. Таким образом, синхронизация pickFile остановит браузер, и пользователь не сможет взаимодействовать с элементами формы. В качестве альтернативы, возврат одного обещания от pickFile, которое выполняется только после завершения загрузки, будет означать, что пользователю пришлось ждать загрузки, даже если файл не нужен немедленно.   -  person robamler    schedule 24.08.2015
comment
@loganfsmyth: я думаю, в этом конкретном случае упаковка возвращенного обещания в объект жизнеспособна, поскольку я уже знаю, что значение, которое я хочу передать, является обещанием. Я просто подумал, что мне не хватает какой-то очевидной альтернативы, так как я думал, что выполнение обещания со значением типа any (будь то обещание или нет) было довольно общим делом, поэтому я не ожидать, что это будет невозможно. (Предположим, вы пишете библиотеку, которая хочет вернуть какое-то определенное пользователем значение из асинхронной операции, где библиотека не может делать никаких предположений о типе значения). Спасибо, в любом случае!   -  person robamler    schedule 24.08.2015
comment
Нет, я не это имел в виду. pickFile, очевидно, асинхронный и возвращает обещание. Это нормально. Но после этого все остальное может быть синхронным. Если вы сделаете это и прикрепите обработчик then только после того, как закончите со всем, кроме загрузки, вы получите необходимое поведение.   -  person Amit    schedule 24.08.2015
comment
Спасибо за быстрый ответ! К сожалению, мне все еще сложно понять ваше решение. Вы предлагаете создать второе обещание из основного кода (т.е. вне функции pickFile)? Затем первое обещание (picker) должно быть разрешено с помощью некоторого объекта, который содержит всю информацию, необходимую для инициирования загрузки, и основной код должен будет знать о реализации средства выбора файлов (например, он должен будет обновить индикатор выполнения в средстве выбора файлов, ...). Конечно, это возможно, но это создает множество зависимостей. Или я неправильно понял ваше предложение?   -  person robamler    schedule 24.08.2015
comment
В общепринятой терминологии «выполнить» означает перевести обещание в состояние успеха (в отличие от «отклонить»). Учитывая это, вы не можете выполнить обещание с помощью обещания. Вы можете вернуть обещание из обработчика .then, и это приведет к замене исходного обещания возвращенным обещанием. Кстати, термин «решимость» обычно означает либо выполнение, либо отказ.   -  person    schedule 29.08.2015
comment
Такого рода проблемы также могут возникнуть с чем-либо, на котором есть метод then(), даже если метод then() не совпадает с then() Promise и объект не предназначен для Promise. Попытка решить с его помощью приводит к вызову then() метода.   -  person interfect    schedule 05.04.2018


Ответы (3)


Кажется, что нет решения, кроме обходного пути, который я уже описал в вопросе. Для справки в будущем: если вы хотите выполнить (а не разрешить) обещание p со значением val, где val - другое обещание, тогда простой вызов функции разрешения обещания для p с аргументом val не будет работать должным образом. Это приведет к тому, что p будет "заблокирован" в состоянии val, так что p будет выполняться со значением разрешения val после выполнения val (см. spec).

Вместо этого оберните val другим объектом и разрешите p этим объектом:

var resolveP;  // Promise resolution function for p

var p = new Promise(
    function(resolve, reject) {
        resolveP = resolve;
    }
);

function fulfillPwithPromise(val) {  // Fulfills p with a promise val
    resolveP({promise: val});
}

p.then(function(res) {
    // Do something as soon as p is fulfilled...

    return res.promise;
}).then(function(res) {
    // Do something as soon as the second promise is fulfilled...
});

Это решение работает, если вы уже знаете, что val является обещанием. Если вы не можете сделать никаких предположений о типе val, то вам, похоже, не повезло. Либо вам нужно всегда переносить значения разрешения обещаний в другой объект, либо вы можете попытаться определить, есть ли у val поле then типа «функция», и условно обернуть его.

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

person robamler    schedule 29.08.2015
comment
Вероятно, было бы лучше дождаться других ответов, прежде чем принимать свой собственный. - person ; 29.08.2015
comment
Эти вопросы и ответы заслуживают большего количества голосов. Это ОГРОМНЫЙ недостаток родных Promises. К счастью, патч прост. - person ; 29.03.2018

Хотя разные люди используют разные термины, в общей терминологии «выполнить» означает перевести обещание в состояние «успех» (в отличие от «отклонить») - состояние, которое будет запускать then обработчики, висящие на нем.

Другими словами, вы не можете «выполнить» обещание с помощью обещания. Вы можете наполнить его ценностью. (Между прочим, термин «решимость» обычно означает либо выполнение, либо отказ.)

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

Вот простой пример этого:

asyncTask1 . then(asyncTask2) . then(processData)

где asyncTask1 - это обещание, а asyncTask2 - функция, возвращающая обещание. Итак, когда asyncTask1 выполнено (выполнено успешно), тогда asyncTask2 выполняется, и обещание, возвращаемое .then, "берется" на себя обещанием asyncTask2, так что когда оно завершается, данные могут быть обработаны.

Я могу сделать нечто подобное, вызвав Promise.resolve с обещанием в качестве параметра. Это немного неправильно, потому что я не разрешаю обещание в техническом смысле. Вместо этого новое созданное обещание «населяется» обещанием, которое я передал. Это также бесполезно, потому что использование результата точно такое же, как использование обещания, которое я передал:

Promise.resolve(asyncTask2)

ведет себя точно так же, как

asyncTask2

(при условии, что asyncTask2 уже является обещанием; в противном случае Promise.resolve создает обещание, которое немедленно выполняется с переданным значением.)

Так же, как вы можете передать обещание Promise.resolve, вы можете передать обещание функции resolve, предоставленной вам в качестве параметра обратного вызова конструктора обещаний. Если параметр, который вы передаете resolve, не является обещанием, обещание немедленно выполняется с этим значением. Однако, если параметр, который вы передаете resolve, является другим обещанием, это обещание «берет на себя тело» создаваемого вами обещания. Другими словами, создаваемое вами обещание начинает вести себя точно так же, как обещание, переданное resolve.

Под «вести себя точно» я имею в виду, что если обещание, которое вы передаете resolve, уже выполнено, обещание, которое вы конструируете, мгновенно выполняется с тем же значением. Если обещание, которое вы передаете resolve, уже отклонено, создаваемое вами обещание немедленно отклоняется по той же причине. Если обещание, которое вы передаете resolve, еще не разрешено, то любые then обработчики, которые вы зависаете от создаваемого обещания, будут вызваны, если и когда обещание, которое вы передаете resolve, будет разрешено.

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

Поэтому, если я не упущу суть вашего вопроса, pickfile можно записать как

function pickFile() {
  return new Promise(function(resolve, reject) {
    ...display the file picker...

    // Mock code to pretend the user picked a file
    window.setTimeout(function() {
        resolve('thefile');
    });
}

Я не совсем понял ваш вопрос, так что это может быть не то, что вам нужно. Пожалуйста, поясните, если вам интересно.

person Community    schedule 29.08.2015
comment
Я думаю, OP действительно хорошо понимает разницу между разрешением и выполнением. Он действительно хочет выполнить обещание, поскольку обещание - это просто ценность. - person Bergi; 29.08.2015
comment
Я понимаю, что в разговорной речи «выполнение и выполнение обещания» иногда используется как синоним. Я использовал эти термины в смысле спецификации ES6, что делает четкое различие между ними. В этом смысле функция resolve названа правильно, как указано в спецификации: обещание разрешается, если оно установлено или если оно было «заблокировано», чтобы соответствовать состоянию другого обещания. (где "урегулировано" означает "выполнено" или "отклонено".) - person robamler; 30.08.2015
comment
Проблема с вашей реализацией заключается в том, что файл может быть недоступен сразу после того, как пользователь выбрал его. Если он все еще должен быть загружен, я хочу, чтобы загрузка была инициирована и контролировалась pickFile, но я все же хочу выполнить обещание, возвращаемое pickFile сразу после того, как пользователь выбрал файл (загрузка должна продолжаться в фоновом режиме). - person robamler; 30.08.2015

Нашел аналогичное решение в процессе перехода от Angular $ q к встроенной функции Promise. Promise.all может быть вариантом (в случаях независимых параллельных асинхронных задач), передавая соответствующий объект или что-то, украшенное состоянием, передавая его всему, что готово, когда это необходимо. В приведенном ниже примере Promise.all обратите внимание, как он восстанавливается в одном из обещаний - мне потребовалось некоторое время, чтобы понять, как перенаправить результат цепочки. Результатом всего этого является возврат последнего обещания. Хотя это не отвечает на заголовок вопроса, использование return Promise.reject(<an-object-including-a-promise>) (или решение) дает серию и / или группу асинхронных задач, разделяемых на общий доступ и управление на этом пути. В случае выбора, загрузки, а затем работы с файлом, я бы взял обработку события прогресса, а затем сделал: pickFile.then(download,orFailGracefully) с downloadProgress, обработанным в обработчике download onResolve (загрузка-прогресс не выглядит асинхронной задачей). Ниже приведены похожие эксперименты в консоли.

var q = {
defer: function _defer(){
    var deferred = { };
    deferred.promise = new Promise(function(resolve, reject){
        deferred.resolve = resolve;
        deferred.reject = reject;
    });
    return deferred;
    }
};

var communityThatCares = q.defer();
communityThatCares.promise.then(function(someGood){
    console.log('someGood', someGood);
    return someGood;
}, function(someBad){
    console.warn('someBad', someBad);
    return someBad;
});

(new Promise(function(resolve, reject){ communityThatCares.about = 'communityThatCares'; setTimeout(resolve,1000, communityThatCares); }))
.then(
function(e){
    console.log(3,e); return e.resolve(e);
}, function(e){
    console.warn(3, e); return e.reject(e);
});

var todo = {
    find:'swan'
};

var greaterGood = [(
(new Promise(function(res,rej){ res(todo); })).then(function(e){ e.stuff = 'things'; return e; }),
(new Promise(function(res,reject){ 
    reject(todo);
})).then(function(e){ return e; }
,function(e){
    console.warn(1,e);
    e.recover = 'uh oh';
    return Promise.resolve(e);
}).then(function(e){ console.log(2,e); return e; }),
(new Promise(function(res,rej){ res(todo); })).then(function(e){ console.log(1,e); e.schedule = 'today'; return e; },function(e){ console.warn(1,e); return e; }).then(function(e){ console.log(2,e); return e; }))
];

var nkay = Promise.all( greaterGood )
.then(function(todo){
    console.log('all',todo[0]); return todo;
}, function(todo){
    console.warn('all',todo[0]); return todo;
});
person jimmont    schedule 22.12.2015
comment
Могу я предложить избежать отложенного антипаттерна, или это намеренная часть вашего решения? - person Bergi; 22.12.2015
comment
Полезные вещи @Bergi. Перед редактированием: вопрос неясен, но, похоже, заключается в том, как разрешить (или отклонить) обещание с другим обещанием или объектом, на который пытается ответить мой ответ. Образец кода не кажется лучшим подходом к заявленной несколько расплывчатой ​​проблеме, т.е. смешивание событий прогресса с асинхронным запросом / обещаниями не имеет для меня смысла. Было бы лучше удалить ответ или просто настроить Promise.all на цепочку promise.then.then (или что-то еще?)? Спасибо! - person jimmont; 24.12.2015