Страницы

Поиск по вопросам

Показаны сообщения с ярлыком promise. Показать все сообщения
Показаны сообщения с ярлыком promise. Показать все сообщения

среда, 5 февраля 2020 г.

Работа промисов

#javascript #promise


Добрый Вечер. Наткнулся на такой пример с промисами :

var promise = new Promise(function(resolve, reject) {
    resolve(1);
});

function logger(message) {
    console.log(message);
}

promise
  .then(logger)
  .then(logger(2))
  .then(() => logger(3));


В результате на выходе результат - 2, 1, 3. Правильно ли я понимаю, что перед тем,
как начинать выполнять данный код, с начала отдается приоритет вызову функции logger
? Именно поэтому в результате с начала идет 2 ? Функция logger, возвращает undefined,
а в консоль пишет 2. Затем, после выполнения промиса, после первого then (который вернул
вместо промиса undefined), в консоль запишется 1, а уж затем вернется 3. 
    


Ответы

Ответ 1



Promise нужен для выполнения действий со значением, которого на момент определения действий может ещё не быть. Это чтобы было немножко понятнее, какова цель всего происходящего здесь. Ещё обращу внимание, что существующие реализации JS однопоточны и переключения в пределах одной исполняющей среды (скажем, вебворкеры я считаю отдельными средами) происходят только между функциями, после завершения выполнения каждой. Разумеется, функции внутри себя могут синхронно (непосредственно) вызывать другие функции, но многое в JS API опирается на асинхронные вызовы, которые лишь приводят к добавлению в очередь событий и, когда-нибудь, обработку циклом событий. Ну да к делу. Вывод у вас происходит при вызове функции logger. Посмотрим, когда она вызывается. Сначала выполняется наружный код. Определение цепочки промисов. Пока выполнение этого участка не дойдёт до самого конца, никакая другая функция вызвана по какому-либо событию (вроде разрешения промиса, что здесь происходит) не будет. promise .then(logger) .then(logger(2)) .then(() => logger(3)); В первом вызове then аргументы разрешаются в значения напрямую, без вычислений. Никаких вызовов logger там не происходит. Пока что. Во втором вызове then аргумент уже выражение, требующее вычисления перед передачей: вызов функции logger. Тут-то и вызывается logger(2) и выводится 2. А возвращается из logger... ничего, что снаружи видно как undefined, которое функцией не является а потому приводит к "сквозному пробрасыванию" результата в следующий then (если таковой, как здесь, имеется), поскольку: If the first argument is omitted or provided a non-function, the new Promise that is created simply adopts the fulfillment state of the Promise that then is called on (if it becomes fulfilled). Если первый аргумент не передан, или в нём передана не функция, то промис наследует состояние разрешения у промиса, на котором then был вызван (если он разрешается). https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then Я б сказал, что игнорирование типа аргумента в then это грубый хак, сбивающий с толку и склоняющий к злоупотреблениям, но у него могут быть свои мотивы. Без большой необходимости кормить в then не-функции не стоит. В третьем вызове аргумент является функциональным выражением (стрелочной функцией, если точно), но для его вычисления вызовы logger не нужны, т. к. его результатом является функция, которая при вызове (но не создании!) вызывает logger и выводит что-то в консоль. А на этом этапе эта функция лишь создалась. А потом уже управление переходит внутрь, поскольку промис требует запуска, его событие ждёт своей очереди на обработку. В зависимости от другого выполняющегося в той же машине JS-кода может быть и не сразу, но в рамках примера другого кода нет, так что тут — сразу внутрь цепочки. Сначала вызывается функция из конструктора Promise. Она приводит к разрешению промиса со значением 1. Затем вызывается logger с аргументом, полученным из прошлого промиса: получается logger(1). А потому происходит вывод 1, а промис разрешается со значением undefined (т. к. функция ничего не вернула). When a value is simply returned from within a then lambda, it will effectively return Promise.resolve(). Затем сталкиваемся с пустым промисом, образованным без функции... фиг знает, что там внутри ещё происходит, но результат прошлого разрешения промиса (undefined) просто считается результатом и у этого и передаётся далее, состояние промиса (разрешился) при этом сохраняется. Затем функция () => logger(3) вызывается с аргументом undefined, но JS наплевать на лишние аргументы и в результате успешно выполняется тело функции, выполняется logger(3) и выводится 3.

Работа resolve в promise

#javascript #ajax #promise


Есть такой код, один аспект которого мне не понятен:



    'use strict';
    
    let promise = new Promise((resolve, reject) => {
      resolve("Промис завершился");
      console.log("Промис ждет, пока я появлюсь");
    });
    
    promise
      .then(
        result => console.log("Fulfilled: " + result), 
        error => console.log("Rejected: " + error) 
      );




Собственно, не понятно то, что алерт в функции-конструкторе вообще отрабатывает своё
и появляется. И сразу же за ним (это прям вообще не понятно - почему за ним?) появляется
"промис завершился".
Не понятно по какому принципу работает resolve(), то есть передается управление обработчику
.then.
Я много раз видел, когда resolve() помещали конкретно внутрь обработчика  onload
(для ajax запросов, например), который сам по себе говорит о завершенности выполнения
вопроса. А получается, что resolve() можно запихнуть хоть с самого начала и код ниже
всё равно отработает.
Да, конечно, в случае с ajax можно ожидать отклонение запроса сервером, поэтому нужно
использовать resolve() в случае успеха, а reject() в случае ошибки.
Помогите разобраться.
    


Ответы

Ответ 1



В JS остановить функцию может только или return, ошибка или её конец. Почитать. В ожидании используется некий асинхронный подход (иначе не вышло бы отлавливать асинхронные операции, Ваш КО): при разрешении обещания, коллбэк встаёт в очередь до завершения ожидающего кода в функции и только потом вызывается. Если Вам этого не нужно, используйте return: return resolve();.

Ответ 2



Промис(обещание) сам по себе никого не ждёт. Просто у вас есть возможность обрабатывать как синхронный, так и асинхронный код. Созданный Promise исполняется сразу же, синхронно(на самом деле это зависит от реализации), а своих подписчиков(then) уведомляет на следующем такте event-loop. Давайте рассмотрим его поведение на примере: 'use strict'; let promise = new Promise((resolve, reject) => { resolve("Промис завершился"); console.log("1. Promise created"); }); console.log('2. After creation'); promise .then( result => console.log("3. Resolved: " + result), error => console.log("Rejected: " + error) ); console.log('2.5 функция в .then тем не менее будет вызвана позже'); Как видите, Promise при создании сразу(синхронно) запустил свой executor, но обработчик в then будет вызван на следующей итерации eventLoop. Пример внутренней реализации(очень урезанной): function Thennable(executor) { let thenList = []; let state = 0; // 1 - resolved, 2 - rejected let value = null; // executor вызывается сразу же, синхронно executor( res => { thenList.forEach(then => // обработчики вызываются асинхронно. setTimeout(() => then[0](res))); state = 1; value = res; }, error => { thenList.forEach(then => setTimeout(() => then[1](error)) ); state = 2; value = error; } ); this.then = (onResolve, onReject) => { if (state == 0) {// ещё не обработано, добавим в очередь ожидающих thenList.push([onResolve, onReject]); } else if (state == 1) { // resolved onResolve(value); } else if (state == 2) { // rejected onReject(value); } // return new Thennable(...) }; } let p = new Thennable((ok, fail) => { ok('resolved'); console.log('1 created'); }); console.log('2 after create'); p.then(res => console.log(3, res))

Ответ 3



resolve() обычная коллбэк функция, вызов который говорит промису перейти в выполненое состояние. resolve() не останавливает выполнение функции внутри промиса.

Ответ 4



Приведу цитату из статьи, что бы не ошибиться: Согласно стандарту, у объекта new Promise(executor) при создании есть четыре внутренних свойства: PromiseState – состояние, вначале «pending». PromiseResult – результат, при создании значения нет. PromiseFulfillReactions – список функций-обработчиков успешного выполнения. PromiseRejectReactions – список функций-обработчиков ошибки. Когда функция-executor вызывает reject или resolve, то PromiseState становится "resolved" или "rejected", а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь "PromiseJobs". Эта очередь автоматически выполняется, когда интерпретатору «нечего делать». То есть resolve не сразу вызывает функцию, а ждет окончания функции-executor, которую передали при создании promise-a

суббота, 4 января 2020 г.

Использование promise с циклом for

#javascript #nodejs #async_programming #promise


Задача: собираю данные геодаты с сервера (не моего). Для этого формирую запрос для
определенного промежутка координат и в цикле for отправляю запрос на сервер. Ответ
записываю в файл и дабы сервер не банил меняю ip адрес через tor-control. Ожидание
ответа реализовано с использованием promise. Это в идеале. На деле (судя по логам в
консоли) данные отправляются как попало. Подскажите где я повернул не туда.

for (var i = xMin; i <= xMax; i++) {
    for (var k = yMin; k <= yMax; k++) {
        var swp = swPoint(k, i);
        var nep = nePoint(k, i);
        var llsw = pointToLatLong(swp[0], swp[1]);
        var llne = pointToLatLong(nep[0], nep[1]);
        var datatosend = 'fromlat=' + llsw[0].toString() + 'tolat=' + llne[0].toString()
+ 'fromlng=' + llsw[1].toString() + 'tolng' + llne[1].toString();

        currX = k;
        currY = i;

        filename = "json/" + currX.toString() + "_" + currY.toString() + "_" + zoom.toString()
+ ".json";

        options.body = datatosend;

        console.log(filename);

        GetData(options)
        .then(body =>{
            return WriteToFile(body, filename);
        })
        .then(() =>{
            return GetNewCircuit();
        })
        .then(() =>{
            console.log('Step finished');
        })
    }
}


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

Текст лога консоли:

json/0_0_1.json
json/1_0_1.json
json/0_1_1.json
json/1_1_1.json
Get response
json/1_1_1.json
Circuit changed
Step finished
Get response
json/1_1_1.json
Circuit changed
Step finished
Get response
json/1_1_1.json
Circuit changed
Step finished
Get response
json/1_1_1.json
Circuit changed
Step finished


Вызываемые методы:

function GetNewCircuit(){
    return new promise(function(resolve, reject){
        new_identity('127.0.0.1', 9051, cookie, function(err){
            if(!err){
               console.log('Circuit changed');
               resolve()
            } else{
                console.log(err);
                reject(err);
            }
        });
    });
}

function GetData(reqOptions){
    return new promise(function(resolve, reject){
        request(reqOptions, function(error,response,body){
            if(!error && response.statusCode == 200){
                console.log('Get response');
                resolve(body);
            }
            else{
                reject(error);
            }
        });
    });
}

function WriteToFile(dataToWrite, fileToWrite){
    return new promise(function(resolve, reject) {
        fs.writeFile(fileToWrite, dataToWrite, function(err) {
           if(!err){
               console.log(fileToWrite);
               resolve();
           }
           else{
               reject(err);
           }
        }); 
    });
}


С js'ом знакомство только начинаю, поэтому если кроме ответа еще и посоветуете где
и как лучше ознакамливаться буду только рад.
    


Ответы

Ответ 1



В коде этом сразу две основных проблемы (из трех) начинающих javascript-разработчиков вижу я. Проблема один - непонимание асинхронности. Разумеется, цикл не ждет возврата результата асинхронной операции, чтобы прокрутиться дальше. Представьте что у вас четыре гиперактивных двортерьера, и вы подряд кидаете им четыре мячика. В каком порядке они их принесут обратно? Да черт его знает. А то что внутри Promise.then, это колбек, только написанный удобнее. Что делать? Не надо: Первое решение которое приходит в голову, а давайте дождемся конца первой операции, а потом уже счетчик цикла увеличим и в следующей итерации запустим следующую операцию. Но этим Вы просто превращаете асинхронную операцию в синхронную и теряете весь профит от асинхронности. Надо: Понять что основная проблема этого кода не в асинхронности, а в том что Вы не понимаете замыкания. Ну то есть вас не смущает что у вас консольложится json/1_1_1.json столько раз? Проблема два: непонимание областей видимости в js, они же замыкания. Вопрос про потерю значения переменной в цикле - топ 1 вопрос на собеседованиях на мидл разработчика, при неответе на который разговор можно сворачивать. На этом ресурсе этот вопрос в разных формулировках встречается пару раз в неделю. Например вот здесь на него подробно ответили (не упоминая block scope в es6 правда). Ну то есть ядро ошибки в вашем коде выглядит как-то так: for(var i = 0; i<4; i++){ setTimeout(function(){console.log(i)}, 1000) } setTimeout тут - как пример простейшей асинхронной операции. Если Вы не понимаете почему тут выведет 4 раза 4 - надо медитировать пока не поймете. Обязательно. Если вкратце, то это происходит потому что скоуп в js для var переменных - это функция, а не фигурные скобочки. Цикл отдельного скоупа не образует и все четыре раза функция будет ссылаться на одну и ту же переменную i, на одну и ту же область памяти. Ок, я все понял, все равно хочу синхронно. Для того чтобы хитро управлять ходом управления множества асинхронных запросов есть ряд распространенных библиотек, например async, посмотрите например на async.waterfall

Ответ 2



Собери промисы в массив в цикле потом дождись их выполнения используя Promise.all

пятница, 27 декабря 2019 г.

Как переводить на русский язык термины future/promise?

#терминология #language_lawyer #promise


Как переводят обычно в русскоязычной литературе слова future и promise?
    


Ответы

Ответ 1



Поскольку вопрос звучит именно "как переводят", а не "как используют" термины, то можно встретить такие варинты переводов: promise - обещание Пример, "создаём обещание выполнить что-то при наступлении такого-то события" - звучит гораздо осмысленнее для русскоговорящего человека, чем "создаём promise выполнить..." Так как речь идёт про терминологию, то скорее всего вместо слова future (сущ, прил. будущее) подразумевалось слово feature. feature - имеет несколько вариантов перевода: особенность, характеристика, черта, функция, средство и т.п, которые могут по-разному использоваться в зависимости от контекста. Примеры: key features - основные (ключевые) характеристики; feature logic - логика признаков.

вторник, 10 декабря 2019 г.

Что такое thenable объект?

#javascript #ecmascript_6 #терминология #promise


Что такое thenable и как он используется?
    


Ответы

Ответ 1



Thenable этот тот же Promise, например, потому что он имеет метод then в виде Promise.prototype.then(). Как подметил vp_arth: henable не обязан быть полноценным Promise. Так, например, Promise.resolve(value) работает по такой логике: if the value is a thenable (i.e. has a "then" method), the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value Если value это промис, то возвращаемый промис(возвращает Promise.resolve) будет следовать логике промиса в аргументе value. Пример: // тут then будет выполнять then для new Promise Promise.resolve( new Promise(resolve => setTimeout(resolve, 1000, 'Выполнился через секунду')) ).then(value => console.log(value)) Иначе будет возвращен промис с уже выполненным значением: // тут передается промис с выполненым(resolve) значением Promise.resolve('Выполнился через секунду').then(value => console.log(value))

Ответ 2



Javascript включает реализацию спецификации Promises/A+ Согласно этой спецификации термин thenable означает следующее: “thenable” is an object or function that defines a then method. “thenable” – это объект или функция, в которых определен метод then.

Ответ 3



Promise.resolve(value): if the value is a thenable (i.e. has a "then" method), the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value Если у промиса есть then, то вся цепочка выполнится. Если then нет, то сразу вернётся значение. // сначала выполнится вся цепочка промиса, а потом вернётся результат var promise1 = Promise.resolve(new Promise((resolve, reject) => { resolve([1, 2, 3]); }).then(res => res)); promise1.then(function(value) { console.log(value); // выведет: Array [1, 2, 3] }); // сразу получаем результат var promise2 = Promise.resolve([1, 2, 3]); promise2.then(function(value) { console.log(value); // выведет: Array [1, 2, 3] });

четверг, 5 декабря 2019 г.

Как выполнить последовательно несколько асинхронных фунций в Javascript без коллбэков?

#javascript #jquery #асинхронность #promise


Как написать простой, понятный, легко обслуживаемый код, который запускает последовательно
несколько асинхронных функций в javascript/jQuery?
(когда отработает одна, должна запускаться другая)

Следующий пример иллюстриует мой вопрос:

function f1(){ setTimeout( function(){ console.log(1); }, 30); }
function f2(){ setTimeout( function(){ console.log(2); }, 20); }
function f3(){ setTimeout( function(){ console.log(3); }, 10); }
f1(); f2(); f3(); 


на выходе 3 2 1
как сделать что-бы выдавало 1 2 3  ?

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

Подобный вопрос задавался не раз, но я почему-то не нашел ответа который бы меня устроил.
    


Ответы

Ответ 1



Если вы хотите использовать Обещания (Promise), то для начала вам нужно модифицировать ваши функции так, чтобы они возвращали Обещания. Например, первая из ваших функций будет иметь вид: function f1() { return new Promise(function(resolve){ setTimeout(function() { console.log(1); resolve(); }, 30); }); } Остальные функции преобразуются аналогичным образом. Теперь у вас есть три функции (f1, f2, f3), которые возвращают Обещания и вы хотите выполнить их последовательно. Если вы не используете библиотек, вроде Bluebird, то вам придется реализовать очередь вызова Обещаний вручную. Это не так сложно, как кажется: // Аргумент "deeds" - это массив функций, которые должны выполняться // последовательно. При этом, каждая функция должна возвращать // Обещание (Promise). var seqRunner = function(deeds) { return deeds.reduce(function(p, deed) { return p.then(function() { // Выполняем следующую функцию только после того, как отработала // предыдущая. return deed(); }); }, Promise.resolve()); // Инициализируем очередь выполнения. } А пользоваться этой очередью нужно вот так: seqRunner([f1, f2, f3]).then(function() { console.log('Done!'); }); А вот и JSFiddle с рабочим примером. Замечание: Если у вас заранее известное, небольшое число функций, то можно вообще обойтись без функции seqRunner и связывать функции вручную: f1().then(function() { return f2(); }).then(function() { return f3(); }).then(function() { console.log('Done!'); });

Ответ 2



Альтернативный вариант, сделать обертку над setTimeout возвращающую Promise, например так: function delay(timeout){ return new Promise(function(r){ setTimeout(r,timeout); }); } Теперь код из вопроса может выглядеть следующим образом: function delay(timeout) { return new Promise(function(r) { setTimeout(r, timeout); }); } function f1() { console.log(1); } function f2() { console.log(2); } function f3() { console.log(3); } delay(3000).then(f1) .then(function(){return delay(2000);}) .then(f2) .then(function(){ return delay(1000);}) .then(f3) .then(function() { console.log('all finish'); }); Или даже так function delay(timeout) { return new Promise(function(r) { setTimeout(r, timeout); }); } function f1() { console.log(1); return 2000 } function f2() { console.log(2); return 1000; } function f3() { console.log(3); } delay(3000).then(f1) .then(delay) .then(f2) .then(delay) .then(f3) .then(function() { console.log('all finish'); }); Если таких функций много, то можно собрать из них массив и с помощью функции reduce собрать один большой Promise function delay(timeout) { return new Promise(function(r) { setTimeout(r, timeout); }); } function f1() { console.log(1); return 2000 } function f2() { console.log(2); return 1000; } function f3() { console.log(3); } [f1, f2, f3].reduce(function(promise, func) { return promise.then(func).then(delay); }, delay(3000)) .then(function() { console.log('all finish') });

Ответ 3



Если каждой добавить таки callback отчёт о выполнении, можно держать очередь в массиве и вызывать следующую ф-ю по завершению предыдущей: function f1(){ setTimeout( function(){ console.log(1); next(); }, 30); } function f2(){ setTimeout( function(){ console.log(2); next(); }, 20); } function f3(){ setTimeout( function(){ console.log(3); next(); }, 10); } var queue = [f1, f2, f3] ,i=0 ,next = function(){ queue[i] && queue[i++]();} ; next(); // 1 2 3

воскресенье, 1 декабря 2019 г.

Как дождаться выполнения нескольких Promise аналогично Promise.all

#javascript #promise


Promise.all возвращает промис, который отклоняется при отклонении любого из переданных
all обещаний. Например следующий код:



const a = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("resolve 1");
    resolve(1);
  }, 2000);
});

const b = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("reject 2");
    reject(2);
  }, 1000);
});

const c = new Promise((resolve, reject) => {
  console.log("resolve");
  resolve(3);
});

Promise.all([a, b, c]).then(
  r => {
    console.log(r);
  },
  () => {
    console.log("err");
  },
);




выведет

resolve 3
reject 2
err
resolve 1


т.е. все завершилось без ожидания завершения промиса a (resolve 1 выводится после
err). Вопрос в том, как дождаться, пока все промисы будут выполнены и/или отклонены?
    


Ответы

Ответ 1



Очень просто: замаскировать ошибку через вызов catch. const a = new Promise((resolve, reject) => { setTimeout(() => { console.log("resolve 1"); resolve(1); }, 2000); }); const b = new Promise((resolve, reject) => { setTimeout(() => { console.log("reject 2"); reject(2); }, 1000); }); const c = new Promise((resolve, reject) => { console.log("resolve 3"); resolve(3); }); Promise.all([a, b, c].map(p => p.catch(x => console.error(x)))).then( r => { console.log(r); } ); Результат: [1, undefined, 3].

Ответ 2



const a = new Promise((resolve, reject) => { setTimeout(() => { console.log("resolve 1"); resolve(1); }, 2000); }); const b = new Promise((resolve, reject) => { setTimeout(() => { console.log("reject 2"); reject(2); }, 1000); }); const c = new Promise((resolve, reject) => { console.log("resolve"); resolve(3); }); Promise.all([a, b, c].map(p => p.then( x => ({ resolved: x }), e => ({ rejected: e }) ))).then(results => { console.log('=== ALL ==='); for (var res of results) { if ('resolved' in res) { // Not res.resolved because `resolve(0)` console.log(res.resolved); } else { console.log('error', res.rejected); } } });

Ответ 3



Вот как-то так получилось (не знаю на сколько это говнокод), я сделал функцию, которая модифицирует промисы, добавляя им свойство isPending, в методе waitForAll я каждому промису добавляю обработчики в then, в которых при успешном/неуспешном выполнени промиса пробегаю входной массив и проверяю, у всех ли промисов в масиве isPending == false, если так, значит все промисы уже завершились. const a = MakePromise(new Promise((resolve, reject) => { setTimeout(() => { console.log("resolve 1"); resolve(1); }, 2000); })); const b = MakePromise(new Promise((resolve, reject) => { setTimeout(() => { console.log("reject 2"); reject(2); }, 1000); })); const c = MakePromise(new Promise((resolve, reject) => { console.log("resolve"); resolve(3); })); waitForAll([a, b, c]); function waitForAll(arr) { arr.forEach((p) => { p.then( (r) => { if (allDone(arr)) console.log('all done'); }, (e) => { if (allDone(arr)) console.log('all done'); }); }); }; function allDone(arr) { for (let i = 0; i < arr.length; i++) { if (arr[i].isPending()) return false; } return true; }; function MakePromise(promise) { var isPending = true; var result = promise.then( (v) => { isPending = false; return v; }, (e) => { isPending = false; throw e; } ); result.isPending = () => { return isPending; }; return result; } Я понял что предыдущий вариант явно не очень, сделал через каунтер, тоесть функция waitForAll получает массив промисов, сразу записываем в переменную длину массива (количество промисов) и в обработчиках then уменьшаем ету переменную ну и сравниваем ее с 0, тоесть если количество 0, значит все промисы выполнены : const a = new Promise((resolve, reject) => { setTimeout(() => { console.log("resolve 1"); resolve(1); }, 2000); }); const b = new Promise((resolve, reject) => { setTimeout(() => { console.log("reject 2"); reject(2); }, 1000); }); const c = new Promise((resolve, reject) => { console.log("resolve"); resolve(3); }); waitForAll([a, b, c]); function waitForAll(arr) { var count = arr.length; arr.forEach((p) => { p.then( (r) => { count--; if (count == 0) console.log('all done'); }, (e) => { count--; if (count == 0) console.log('all done'); }); }); };

пятница, 19 апреля 2019 г.

Работа resolve в promise

Есть такой код, один аспект которого мне не понятен:
'use strict'; let promise = new Promise((resolve, reject) => { resolve("Промис завершился"); console.log("Промис ждет, пока я появлюсь"); }); promise .then( result => console.log("Fulfilled: " + result), error => console.log("Rejected: " + error) );
Собственно, не понятно то, что алерт в функции-конструкторе вообще отрабатывает своё и появляется. И сразу же за ним (это прям вообще не понятно - почему за ним?) появляется "промис завершился". Не понятно по какому принципу работает resolve(), то есть передается управление обработчику .then Я много раз видел, когда resolve() помещали конкретно внутрь обработчика onload (для ajax запросов, например), который сам по себе говорит о завершенности выполнения вопроса. А получается, что resolve() можно запихнуть хоть с самого начала и код ниже всё равно отработает. Да, конечно, в случае с ajax можно ожидать отклонение запроса сервером, поэтому нужно использовать resolve() в случае успеха, а reject() в случае ошибки. Помогите разобраться.


Ответ

В JS остановить функцию может только или return, ошибка или её конец. Почитать
В ожидании используется некий асинхронный подход (иначе не вышло бы отлавливать асинхронные операции, Ваш КО): при разрешении обещания, коллбэк встаёт в очередь до завершения ожидающего кода в функции и только потом вызывается.
Если Вам этого не нужно, используйте return: return resolve();

вторник, 9 апреля 2019 г.

Работа промисов

Добрый Вечер. Наткнулся на такой пример с промисами :
var promise = new Promise(function(resolve, reject) { resolve(1); });
function logger(message) { console.log(message); }
promise .then(logger) .then(logger(2)) .then(() => logger(3));
В результате на выходе результат - 2, 1, 3. Правильно ли я понимаю, что перед тем, как начинать выполнять данный код, с начала отдается приоритет вызову функции logger ? Именно поэтому в результате с начала идет 2 ? Функция logger, возвращает undefined, а в консоль пишет 2. Затем, после выполнения промиса, после первого then (который вернул вместо промиса undefined), в консоль запишется 1, а уж затем вернется 3.


Ответ

Promise нужен для выполнения действий со значением, которого на момент определения действий может ещё не быть. Это чтобы было немножко понятнее, какова цель всего происходящего здесь.
Ещё обращу внимание, что существующие реализации JS однопоточны и переключения в пределах одной исполняющей среды (скажем, вебворкеры я считаю отдельными средами) происходят только между функциями, после завершения выполнения каждой. Разумеется, функции внутри себя могут синхронно (непосредственно) вызывать другие функции, но многое в JS API опирается на асинхронные вызовы, которые лишь приводят к добавлению в очередь событий и, когда-нибудь, обработку циклом событий.
Ну да к делу.
Вывод у вас происходит при вызове функции logger. Посмотрим, когда она вызывается.
Сначала выполняется наружный код. Определение цепочки промисов. Пока выполнение этого участка не дойдёт до самого конца, никакая другая функция вызвана по какому-либо событию (вроде разрешения промиса, что здесь происходит) не будет.
promise .then(logger) .then(logger(2)) .then(() => logger(3));
В первом вызове then аргументы разрешаются в значения напрямую, без вычислений. Никаких вызовов logger там не происходит. Пока что. Во втором вызове then аргумент уже выражение, требующее вычисления перед передачей: вызов функции logger. Тут-то и вызывается logger(2) и выводится 2 А возвращается из logger... ничего, что снаружи видно как undefined, которое функцией не является а потому приводит к "сквозному пробрасыванию" результата в следующий then (если таковой, как здесь, имеется), поскольку:
If the first argument is omitted or provided a non-function, the new Promise that is created simply adopts the fulfillment state of the Promise that then is called on (if it becomes fulfilled). Если первый аргумент не передан, или в нём передана не функция, то промис наследует состояние разрешения у промиса, на котором then был вызван (если он разрешается). https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
Я б сказал, что игнорирование типа аргумента в then это грубый хак, сбивающий с толку и склоняющий к злоупотреблениям, но у него могут быть свои мотивы. Без большой необходимости кормить в then не-функции не стоит. В третьем вызове аргумент является функциональным выражением (стрелочной функцией, если точно), но для его вычисления вызовы logger не нужны, т. к. его результатом является функция, которая при вызове (но не создании!) вызывает logger и выводит что-то в консоль. А на этом этапе эта функция лишь создалась.
А потом уже управление переходит внутрь, поскольку промис требует запуска, его событие ждёт своей очереди на обработку. В зависимости от другого выполняющегося в той же машине JS-кода может быть и не сразу, но в рамках примера другого кода нет, так что тут — сразу внутрь цепочки.
Сначала вызывается функция из конструктора Promise. Она приводит к разрешению промиса со значением 1 Затем вызывается logger с аргументом, полученным из прошлого промиса: получается logger(1). А потому происходит вывод 1, а промис разрешается со значением undefined (т. к. функция ничего не вернула).
When a value is simply returned from within a then lambda, it will effectively return Promise.resolve() Затем сталкиваемся с пустым промисом, образованным без функции... фиг знает, что там внутри ещё происходит, но результат прошлого разрешения промиса (undefined) просто считается результатом и у этого и передаётся далее, состояние промиса (разрешился) при этом сохраняется. Затем функция () => logger(3) вызывается с аргументом undefined, но JS наплевать на лишние аргументы и в результате успешно выполняется тело функции, выполняется logger(3) и выводится 3

вторник, 9 октября 2018 г.

Как выполнить последовательно несколько асинхронных фунций в Javascript без коллбэков?

Как написать простой, понятный, легко обслуживаемый код, который запускает последовательно несколько асинхронных функций в javascript/jQuery? (когда отработает одна, должна запускаться другая)
Следующий пример иллюстриует мой вопрос:
function f1(){ setTimeout( function(){ console.log(1); }, 30); } function f2(){ setTimeout( function(){ console.log(2); }, 20); } function f3(){ setTimeout( function(){ console.log(3); }, 10); } f1(); f2(); f3();
на выходе 3 2 1 как сделать что-бы выдавало 1 2 3 ?
Желательно без коллбеков - т.к. если надо запустить последовательно больше двух функций это уже тяжело читать. Многое говорит о том что возможно решение с помощью обьекта $.Deferred, но пока не видел разумного варианта.
Подобный вопрос задавался не раз, но я почему-то не нашел ответа который бы меня устроил.


Ответ

Если вы хотите использовать Обещания (Promise), то для начала вам нужно модифицировать ваши функции так, чтобы они возвращали Обещания. Например, первая из ваших функций будет иметь вид:
function f1() { return new Promise(function(resolve){ setTimeout(function() { console.log(1); resolve(); }, 30); }); }
Остальные функции преобразуются аналогичным образом.
Теперь у вас есть три функции (f1, f2, f3), которые возвращают Обещания и вы хотите выполнить их последовательно. Если вы не используете библиотек, вроде Bluebird, то вам придется реализовать очередь вызова Обещаний вручную. Это не так сложно, как кажется:
// Аргумент "deeds" - это массив функций, которые должны выполняться // последовательно. При этом, каждая функция должна возвращать // Обещание (Promise). var seqRunner = function(deeds) { return deeds.reduce(function(p, deed) { return p.then(function() { // Выполняем следующую функцию только после того, как отработала // предыдущая. return deed(); }); }, Promise.resolve()); // Инициализируем очередь выполнения. }
А пользоваться этой очередью нужно вот так:
seqRunner([f1, f2, f3]).then(function() { console.log('Done!'); });
А вот и JSFiddle с рабочим примером

Замечание:
Если у вас заранее известное, небольшое число функций, то можно вообще обойтись без функции seqRunner и связывать функции вручную:
f1().then(function() { return f2(); }).then(function() { return f3(); }).then(function() { console.log('Done!'); });

Как дождаться выполнения нескольких Promise аналогично Promise.all

Promise.all возвращает промис, который отклоняется при отклонении любого из переданных all обещаний. Например следующий код:
const a = new Promise((resolve, reject) => { setTimeout(() => { console.log("resolve 1"); resolve(1); }, 2000); }); const b = new Promise((resolve, reject) => { setTimeout(() => { console.log("reject 2"); reject(2); }, 1000); }); const c = new Promise((resolve, reject) => { console.log("resolve"); resolve(3); }); Promise.all([a, b, c]).then( r => { console.log(r); }, () => { console.log("err"); }, );
выведет
resolve 3 reject 2 err resolve 1
т.е. все завершилось без ожидания завершения промиса a (resolve 1 выводится после err). Вопрос в том, как дождаться, пока все промисы будут выполнены и/или отклонены?


Ответ

Очень просто: замаскировать ошибку через вызов catch
const a = new Promise((resolve, reject) => { setTimeout(() => { console.log("resolve 1"); resolve(1); }, 2000); }); const b = new Promise((resolve, reject) => { setTimeout(() => { console.log("reject 2"); reject(2); }, 1000); }); const c = new Promise((resolve, reject) => { console.log("resolve 3"); resolve(3); }); Promise.all([a, b, c].map(p => p.catch(x => console.error(x)))).then( r => { console.log(r); } );
Результат: [1, undefined, 3].