Як отримати доступ до попередніх результатів обіцянки в ланцюзі .then ()?


650

Я реструктуризував свій код до обіцянок і створив чудовий довгий плоский ланцюжок обіцянок , що складається з декількох .then()зворотних викликів. Зрештою, я хочу повернути деяке складене значення і мені потрібно отримати доступ до декількох проміжних результатів обіцянки . Однак значення роздільної здатності від середини послідовності не входять в обсяг останнього зворотного дзвінка, як я отримую доступ до них?

function getExample() {
    return promiseA(…).then(function(resultA) {
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        return // How do I gain access to resultA here?
    });
}

2
Це запитання дійсно цікаве, і навіть якщо воно позначене тегом javascript, воно є актуальним іншою мовою. Я просто використовую відповідь " Розбий
gontard

Відповіді:


377

Розриваємо ланцюжок

Коли вам потрібно отримати доступ до проміжних значень у вашій ланцюжку, ви повинні розділити ланцюг на частини, які вам потрібні. Замість того, щоб приєднувати один зворотний виклик і якось намагатися використовувати його параметр кілька разів, додайте кілька зворотних дзвінків до однієї і тієї ж обіцянки - де б вам не було значення результату. Не забувайте, обіцянка просто представляє (проксі) майбутню цінність ! Поруч із отриманням однієї обіцянки від іншої в лінійному ланцюжку, використовуйте комбінатори обіцянок, які вам надає ваша бібліотека, щоб побудувати значення результату.

Це призведе до прямого керування потоком, чіткого складу функціональних можливостей і, отже, легкої модуляції.

function getExample() {
    var a = promiseA(…);
    var b = a.then(function(resultA) {
        // some processing
        return promiseB(…);
    });
    return Promise.all([a, b]).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Замість деструктуризации параметра в функції зворотного виклику після того, Promise.allщо став доступний тільки з ES6, в ES5 thenвиклик буде замінений витонченим допоміжним методом , який був наданий багатьма бібліотеками обіцянки ( Q , Bluebird , коли , ...): .spread(function(resultA, resultB) { ….

Bluebird також має спеціальну joinфункцію , щоб замінити цю Promise.all+ spreadкомбінацію з більш простий (і більш ефективної) конструкцією:


return Promise.join(a, b, function(resultA, resultB) {  });

1
Чи виконуються функції всередині масиву в порядку?
scaryguy

6
@scaryguy: У масиві функцій немає, це обіцянки. promiseAі promiseBє тут функціями (повернення обіцянок).
Бергі

2
@Roland Ніколи не говорив, що це :-) Ця відповідь була написана в епоху ES5, де взагалі ніяких обіцянок не було в стандарті, і spreadбуло дуже корисно в цій схемі. Більш сучасні рішення див. У прийнятій відповіді. Однак я вже оновив чітку прохідну відповідь , і справді немає вагомих причин не оновлювати і цю.
Бергі

1
@reify Ні, ви не повинні цього робити , це призведе до проблем з відхиленнями.
Бергі

1
Я не розумію цього прикладу. Якщо є ланцюжок тверджень "тоді", які вимагають, щоб значення поширювались по ланцюгу, я не бачу, як це вирішує проблему. Обіцянка, яка вимагає попереднього значення CANNOT, буде знята (створена), поки це значення не буде присутнє. Крім того, Promise.all () просто чекає завершення всіх обіцянок у своєму списку: замовлення не накладає. Тому мені потрібна кожна «наступна» функція, щоб мати доступ до всіх попередніх значень, і я не бачу, як це робить ваш приклад. Ви повинні пройти нас своїм прикладом, тому що я не вірю в це чи не розумію.
Девід Спектор

238

Гармонія ECMAScript

Звичайно, цю проблему визнали і мовні дизайнери. Вони зробили багато роботи, і пропозиція про функції асинхронізації нарешті втілила її

ECMAScript 8

Вам більше не потрібна одна thenфункція виклику або зворотного дзвінка, оскільки в асинхронній функції (яка повертає обіцянку при виклику) ви можете просто чекати, коли обіцянки будуть вирішені безпосередньо. Він також містить довільні структури управління, такі як умови, цикли та пробні пропозиції, але для зручності нам тут вони не потрібні:

async function getExample() {
    var resultA = await promiseA(…);
    // some processing
    var resultB = await promiseB(…);
    // more processing
    return // something using both resultA and resultB
}

ECMAScript 6

Поки ми чекали ES8, ми вже використовували дуже подібний синтаксис. ES6 оснащений функціями генератора , які дозволяють розділити виконання на частини за умовно розміщеними yieldключовими словами. Ці фрагменти можна виконувати один за одним, незалежно, навіть асинхронно - і це саме те, що ми робимо, коли хочемо чекати вирішення обіцянки, перш ніж виконати наступний крок.

Існують виділені бібліотеки (наприклад, co або task.js ), але також багато бібліотек з обіцянками мають допоміжні функції ( Q , Bluebird , коли ,…), які виконують це асинхронне покрокове виконання для вас, коли ви надаєте їм функцію генератора, дає обіцянки.

var getExample = Promise.coroutine(function* () {
//               ^^^^^^^^^^^^^^^^^ Bluebird syntax
    var resultA = yield promiseA(…);
    // some processing
    var resultB = yield promiseB(…);
    // more processing
    return // something using both resultA and resultB
});

Це працювало в Node.js з версії 4.0, а також декілька браузерів (або їхніх версій розробників) порівняно рано підтримували синтаксис генератора.

ECMAScript 5

Однак, якщо ви хочете / потрібно бути сумісними назад, ви не можете використовувати їх без транспілера. Як функції генератора, так і функції асинхронізації підтримуються поточним інструментом, див., Наприклад, документацію Babel щодо генераторів та функцій асинхронізації .

А потім є також багато інших мов компіляції в JS , які присвячені спрощенню асинхронного програмування. Вони зазвичай використовують синтаксис , аналогічний await, (наприклад , заморожений CoffeeScript ), але є й інші , які показують Haskell-як do-notation (наприклад LatteJs , одномісний , PureScript або LispyScript ).


@Bergi чи потрібно чекати іспиту функції асинхрології getExample () із зовнішнього коду?
arisalexis

@arisalexis: Так, getExampleце все-таки функція, яка повертає обіцянку, працює так само, як функції в інших відповідях, але з кращим синтаксисом. Ви можете awaitзателефонувати в іншу asyncфункцію або ланцюг .then()на її результат.
Бергі

1
Мені цікаво, чому ви відповіли на власне запитання відразу після того, як його задали? Тут є хороша дискусія, але мені цікаво. Можливо, ви знайшли відповіді самостійно після запитання?
Гранмое

@granmoe: Я цілком обговорював всю дискусію як канонічну копію цілі
Бергі

Чи є (не надто трудомісткий) спосіб уникнути використання Promise.coroutine (тобто, не використовуючи Bluebird чи іншу бібліотеку, а лише звичайний JS) у прикладі ECMAScript 6 з функцією генератора? Я мав на увазі щось подібне, steps.next().value.then(steps.next)...але це не спрацювало.
учень не має імені

102

Синхронна перевірка

Присвоєння змінним обіцянок для необхідних пізніших значень, а потім отримання їх значення за допомогою синхронної перевірки. У прикладі використовується .value()метод bluebird, але багато бібліотек надають подібний метод.

function getExample() {
    var a = promiseA(…);

    return a.then(function() {
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // a is guaranteed to be fulfilled here so we can just retrieve its
        // value synchronously
        var aValue = a.value();
    });
}

Це можна використовувати для будь-якої кількості значень:

function getExample() {
    var a = promiseA(…);

    var b = a.then(function() {
        return promiseB(…)
    });

    var c = b.then(function() {
        return promiseC(…);
    });

    var d = c.then(function() {
        return promiseD(…);
    });

    return d.then(function() {
        return a.value() + b.value() + c.value() + d.value();
    });
}

6
Це моя улюблена відповідь: читабельна, розширювана та мінімальна залежність від можливостей бібліотеки чи мови
Jason

13
@Jason: Так, " мінімальна залежність від можливостей бібліотеки "? Синхронна перевірка - це функція бібліотеки та досить нестандартна для завантаження.
Бергі

2
Я думаю, він мав на увазі особливості бібліотеки
смерть

54

Гніздування (і) закриття

Використання закриттів для підтримки області змінних (у нашому випадку параметри функції зворотного виклику успіху) є природним рішенням JavaScript. Обіцяючи, ми можемо довільно вкладати і згладжувати .then() зворотні дзвінки - вони семантично рівноцінні, за винятком сфери внутрішнього.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(function(resultB) {
            // more processing
            return // something using both resultA and resultB;
        });
    });
}

Звичайно, це побудова піраміди відступу. Якщо відступ стає занадто великим, ви все одно можете застосувати старі інструменти для протидії піраміді приреченості : модулювати, використовувати додаткові названі функції та вирівнювати ланцюжок обіцянок, як тільки вам більше не потрібна змінна.
Теоретично, ви завжди можете уникнути більш ніж двох рівнів гніздування (зробивши всі закриття явними), на практиці використовуйте стільки, скільки є розумним.

function getExample() {
    // preprocessing
    return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
    return function(resultA) {
        // some processing
        return promiseB(…).then(makeBhandler(resultA, …));
    };
}
function makeBhandler(resultA, …) {
    return function(resultB) {
        // more processing
        return // anything that uses the variables in scope
    };
}

Ви також можете використовувати допоміжні функції для такого виду часткового застосування , як _.partialвід Underscore / lodash або власного .bind()методу , для подальшого зменшення відступу:

function getExample() {
    // preprocessing
    return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
    // some processing
    return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
    // more processing
    return // anything that uses resultA and resultB
}

5
Ця ж пропозиція подана як рішення " Похибної помилки №4" у статті Нолана Лоусона про обіцянки pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html . Це добре читати.
Роберт

2
Саме ця bindфункція в Monads. Haskell надає синтаксичний цукор (do-notation), щоб він виглядав як синтаксис асинхрон / очікування.
zeronone

50

Явне проходження

Подібно до вставки зворотних викликів, ця методика спирається на закриття. І все ж ланцюг залишається рівним - замість того, щоб передавати лише останній результат, для кожного кроку передається якийсь об'єкт стану. Ці об'єкти стану акумулюють результати попередніх дій, передаючи всі значення, які знадобляться згодом, плюс результат поточного завдання.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Тут ця маленька стрілка b => [resultA, b]- це функція, що закривається resultA, і передає масив обох результатів на наступний крок. Який використовує синтаксис деструктуризації параметрів, щоб знову розбити його на окремі змінні.

Перш ніж деструктивність стала доступною за допомогою ES6, .spread()багато бібліотек з обіцянками пропонували чудовий допоміжний метод ( Q , Bluebird , коли ,…). Він використовує функцію з декількома параметрами - по одному для кожного елемента масиву - для використання в якості .spread(function(resultA, resultB) { ….

Звичайно, необхідне тут закриття може бути додатково спрощене деякими допоміжними функціями, наприклад

function addTo(x) {
    // imagine complex `arguments` fiddling or anything that helps usability
    // but you get the idea with this simple one:
    return res => [x, res];
}


return promiseB(…).then(addTo(resultA));

Крім того, ви можете використовувати Promise.allдля створення обіцянки для масиву:

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
                                                    // as if passed to Promise.resolve()
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

І ви можете використовувати не тільки масиви, але і довільно складні об'єкти. Наприклад, з _.extendабо Object.assignв іншій функції помічника:

function augment(obj, name) {
    return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(augment({resultA}, "resultB"));
    }).then(function(obj) {
        // more processing
        return // something using both obj.resultA and obj.resultB
    });
}

Хоча ця модель гарантує рівний ланцюг, а явні об'єкти стану можуть покращити ясність, для довгого ланцюга він стане стомлюючим. Особливо, коли вам потрібна держава лише спорадично, вам все одно доведеться проходити її через кожен крок. За допомогою цього фіксованого інтерфейсу одиничні зворотні виклики в ланцюзі досить щільно з'єднані і негнучкі до змін. Це ускладнює розбиття на один крок, і зворотні виклики не можуть надходити безпосередньо з інших модулів - їх завжди потрібно загорнути в кодовий код, який піклується про стан. Абстрактні функції помічника, як описано вище, можуть трохи полегшити біль, але він завжди буде присутній.


По-перше, я не думаю, що синтаксис, який опускає цей Promise.allслід, слід заохочувати (він не працюватиме в ES6, коли деструктурування замінить його і переключення .spreadна "а" thenдає людям часто несподівані результати. Щодо збільшення - я не впевнений, навіщо вам це потрібно використовувати розширення - додавання речей до прототипу обіцянки не прийнятний спосіб поширити обіцянки ES6 у будь-якому випадку, які, як передбачається, будуть розширені (підпорядкованою зараз) підкласою.
Бенджамін Груенбаум

@BenjaminGruenbaum: Що ви маєте на увазі під " опусканням синтаксисуPromise.all "? Жоден із методів у цій відповіді не порушиться з ES6. Перехід spreadдо руйнування thenтакож не повинен мати проблем. Re .prototype.augment: Я знав, що хтось це помітить, мені просто подобалося вивчити можливості - збираюся редагувати це.
Бергі

Під синтаксисом масиву я маю на увазі, return [x,y]; }).spread(...замість return Promise.all([x, y]); }).spread(...якого він не змінився б, коли поміняти розповсюдження на цукор, що руйнує es6, а також не був би дивним випадком, коли обіцянки трактують повернення масивів інакше від усього іншого.
Бенджамін Груенбаум

3
Це, мабуть, найкраща відповідь. Обіцянки - "функціональне реактивне програмування" - світло, і це часто використовується рішення. Наприклад, BaconJs має #combineTemplate, який дозволяє об'єднати результати в об’єкт, який передається вниз по ланцюгу
U Avalos,

1
@CapiEtheriel Відповідь була написана, коли ES6 не був настільки широко розповсюдженим, як сьогодні. Так, можливо, прийшов час поміняти приклади
Бергі,

35

Зміна контекстуального стану

Тривіальне (але неелегантне та досить помилкове) рішення - просто використовувати змінні більш високого масштабу (до яких усі зворотні виклики у ланцюжку мають доступ) та записувати до них значення результатів, коли ви отримуєте їх:

function getExample() {
    var resultA;
    return promiseA(…).then(function(_resultA) {
        resultA = _resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both resultA and resultB
    });
}

Замість багатьох змінних можна також використовувати (спочатку порожній) об'єкт, на якому результати зберігаються як динамічно створені властивості.

Це рішення має ряд недоліків:

  • Стан, що змінюється, некрасивий , і змінюється, глобальні змінні - злі .
  • Ця закономірність не працює через межі функцій, модулювання функцій важче, оскільки їх декларації не повинні залишати спільну область застосування
  • Область змінних не перешкоджає доступу до них перед їх ініціалізацією. Це особливо ймовірно для складних конструкцій з обіцянками (петлі, розгалуження, виключення), де можуть статися умови перегонів. Явно передаючи стан, декларативний дизайн, який обіцяє заохочувати, примушує більш чистий стиль кодування, який може запобігти цьому.
  • Треба правильно вибрати область для цих загальних змінних. Вона повинна бути локальною для виконуваної функції, щоб запобігти перегоновим умовам між декількома паралельними викликами, як це було б, наприклад, якщо стан зберігався в екземплярі.

Бібліотека Bluebird заохочує використання об'єкта, який передається разом, використовуючи їх bind()метод для призначення контекстного об'єкта ланцюжку обіцянок. Він буде доступний для кожної функції зворотного дзвінка через інакше непридатне thisключове слово . Хоча властивості об'єкта більш схильні до невідкритих друкарських помилок, ніж змінних, модель є досить розумною:

function getExample() {
    return promiseA(…)
    .bind({}) // Bluebird only!
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }).bind(); // don't forget to unbind the object if you don't want the
               // caller to access it
}

Цей підхід може бути легко змодельований у бібліотеках з обіцянками, які не підтримують .bind (хоча дещо більш багатослівним способом і не можуть бути використані у виразі):

function getExample() {
    var ctx = {};
    return promiseA(…)
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }.bind(ctx)).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }.bind(ctx));
}

.bind()не потрібен для запобігання витоку пам’яті
Esailija

@Esailija: Але чи не повернута обіцянка посилається на контекстний об'єкт інакше? Гаразд, звичайно збирання сміття впорається з цим пізніше; це не "витік", якщо обіцянка ніколи не буде виконана.
Бергі

Так, але обіцянки також посилаються на їхні значення виконання та причини помилок ... але нічого не посилається на обіцянку, так що це не має значення
Esailija

4
Будь ласка, розбийте цю відповідь на два, оскільки я майже проголосував за преамбулою! Я думаю, що "тривіальне (але неелегантне та досить помилкове) рішення" є найчистішим та найпростішим рішенням, оскільки воно більше не покладається на закриття та змінне стану, ніж на вашу прийняту самовідповідь, але все ж простіше. Закриття не є ні глобальним, ні злим. Аргументи, викладені проти такого підходу, для мене не мають сенсу, враховуючи передумови. Які проблеми з модулярізацією можуть бути надані "чудовим довгим плоским ланцюгом обіцянок"?
стаксель

2
Як я вже говорив вище, обіцянки - це "функціональне реактивне програмування". Це анти-модель у FRP
U Avalos

15

Менш різкий крутіж на "Зміна контекстуального стану"

Використання об'єкта на локальному рівні для збору проміжних результатів у ланцюжку обіцянок є розумним підходом до поставленого вами питання. Розглянемо наступний фрагмент:

function getExample(){
    //locally scoped
    const results = {};
    return promiseA(paramsA).then(function(resultA){
        results.a = resultA;
        return promiseB(paramsB);
    }).then(function(resultB){
        results.b = resultB;
        return promiseC(paramsC);
    }).then(function(resultC){
        //Resolve with composite of all promises
        return Promise.resolve(results.a + results.b + resultC);
    }).catch(function(error){
        return Promise.reject(error);
    });
}
  • Глобальні змінні погані, тому це рішення використовує локальну змінну, яка не завдає шкоди. Він доступний лише у межах функції.
  • Стан, що змінюється, некрасивий, але це не мутує стан некрасиво. Некрасивий стан, що змінюється, традиційно посилається на зміну стану аргументів функції або глобальних змінних, але такий підхід просто модифікує стан локально зміненої змінної, яка існує з єдиною метою агрегування результатів обіцянок ... змінної, яка загине простою смертю як тільки обіцянка вирішиться.
  • Проміжні обіцянки не заважають отримати доступ до стану об’єкта результатів, але це не вводить страшного сценарію, коли одна з обіцянок у ланцюжку піде на шахрайство і саботує ваші результати. Відповідальність за встановлення значень на кожному кроці обіцянки обмежується цією функцією, і загальний результат буде або правильним, або неправильним ... це не буде якась помилка, яка з’явиться через роки у виробництві (якщо ви цього не маєте намір !)
  • Це не вводить сценарій умови гонки, який виникне при паралельному виклику, оскільки для кожного виклику функції getExample створюється новий екземпляр змінної результатів.

1
Принаймні уникайте Promiseконструктора антипатерні !
Бергі

Дякую @Bergi, я навіть не зрозумів, що це анти-модель, поки ти не згадав про це!
Джей

це вдале рішення для пом’якшення помилок, пов’язаних із обіцянками. Я використовував ES5 і не хотів додавати іншу бібліотеку для роботи з обіцянкою.
nilakantha singh deo

8

Вузол 7.4 тепер підтримує виклики асинхронізації / очікування з прапором гармонії.

Спробуйте це:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

і запустіть файл за допомогою:

node --harmony-async-await getExample.js

Простий, як може бути!


8

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

Згідно з обіцянками як-до-ланцюжка-javascript

ОК, давайте подивимось на код:

const firstPromise = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('first promise is completed');
            resolve({data: '123'});
        }, 2000);
    });
};

const secondPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('second promise is completed');
            resolve({newData: `${someStuff.data} some more data`});
        }, 2000);
    });
};

const thirdPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('third promise is completed');
            resolve({result: someStuff});
        }, 2000);
    });
};

firstPromise()
    .then(secondPromise)
    .then(thirdPromise)
    .then(data => {
        console.log(data);
    });

4
Це насправді не відповідає на питання про те, як отримати доступ до попередніх результатів у ланцюжку.
Бергі

2
Кожна обіцянка може отримати попереднє значення, у чому ваш зміст?
yzfdjzwl

1
Погляньте на код у питанні. Мета полягає не в тому, щоб отримати результат обіцянки, яку .thenзакликають, а результат раніше. Наприклад, thirdPromiseдоступ до результату firstPromise.
Бергі

6

Ще одна відповідь, використовуючи babel-nodeверсію <6

Використання async - await

npm install -g babel@5.6.14

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Тоді біжи babel-node example.jsі вуаляй!


1
Так, я зробив відразу після того, як я розмістив свою. Все-таки я збираюся залишити його, оскільки це пояснює, як насправді встати та працювати з використанням ES7, а не просто сказати, що коли-небудь ES7 буде доступний.
Ентоні

1
Ну правильно, я повинен оновити свою відповідь, щоб сказати, що "експериментальні" плагіни для них уже є .
Бергі

2

Я не збираюся використовувати цей шаблон у власному коді, оскільки я не є великим прихильником використання глобальних змінних. Однак у крайньому випадку це спрацює.

Користувач - це багатообіцяна модель Mongoose.

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});

2
Зверніть увагу , що ця модель уже докладно в мутабельном контекстну державного відповіді (а також , чому це некрасиво - я не великий шанувальник або)
Берги

У вашому випадку, здається, модель є марною. Вам це зовсім не потрібно globalVar, просто робіть User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });?
Бергі

1
Мені це не потрібно в особистому коді, але користувачеві може знадобитися запустити більше асинхронізації у другій функції, а потім взаємодіяти з оригінальним викликом Promise. Але, як згадувалося, в цьому випадку я буду використовувати генератори. :)
Ентоні

2

Ще одна відповідь за допомогою послідовного виконавця nsynjs :

function getExample(){

  var response1 = returnPromise1().data;

  // promise1 is resolved at this point, '.data' has the result from resolve(result)

  var response2 = returnPromise2().data;

  // promise2 is resolved at this point, '.data' has the result from resolve(result)

  console.log(response, response2);

}

nynjs.run(getExample,{},function(){
    console.log('all done');
})

Оновлення: доданий робочий приклад

function synchronousCode() {
     var urls=[
         "https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
     ];
     for(var i=0; i<urls.length; i++) {
         var len=window.fetch(urls[i]).data.text().data.length;
         //             ^                   ^
         //             |                   +- 2-nd promise result
         //             |                      assigned to 'data'
         //             |
         //             +-- 1-st promise result assigned to 'data'
         //
         console.log('URL #'+i+' : '+urls[i]+", length: "+len);
     }
}

nsynjs.run(synchronousCode,{},function(){
    console.log('all done');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>


1

Під час використання bluebird ви можете використовувати .bindметод для обміну змінними у ланцюжку обіцянок:

somethingAsync().bind({})
.spread(function (aValue, bValue) {
    this.aValue = aValue;
    this.bValue = bValue;
    return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
    return this.aValue + this.bValue + cValue;
});

будь ласка, перевірте це посилання для отримання додаткової інформації:

http://bluebirdjs.com/docs/api/promise.bind.html


Зверніть увагу , що ця модель уже докладно в мутабельном контекстному державному відповіді
Берги

1
function getExample() {
    var retA, retB;
    return promiseA(…).then(function(resultA) {
        retA = resultA;
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        //retA is value of promiseA
        return // How do I gain access to resultA here?
    });
}

простий спосіб: D


Ви помітили цю відповідь ?
Бергі

1

Я думаю, ви можете використовувати хеш RSVP.

Щось таке, як нижче:

    const mainPromise = () => {
        const promise1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('first promise is completed');
                resolve({data: '123'});
            }, 2000);
        });

        const promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('second promise is completed');
                resolve({data: '456'});
            }, 2000);
        });

        return new RSVP.hash({
              prom1: promise1,
              prom2: promise2
          });

    };


   mainPromise()
    .then(data => {
        console.log(data.prom1);
        console.log(data.prom2);
    });

Так, це те саме, що і Promise.allрішення , лише з об'єктом замість масиву.
Бергі

0

Рішення:

Ви можете помістити проміжні значення в область дії в будь-яку пізню функцію "тоді", використовуючи "прив'язувати". Це приємне рішення, яке не потребує зміни способів роботи Обіцянь і вимагає лише рядка чи коду для розповсюдження значень так, як помилки вже розповсюджуються.

Ось повний приклад:

// Get info asynchronously from a server
function pGetServerInfo()
    {
    // then value: "server info"
    } // pGetServerInfo

// Write into a file asynchronously
function pWriteFile(path,string)
    {
    // no then value
    } // pWriteFile

// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
    {
    var scope={localInfo:localInfo}; // Create an explicit scope object
    var thenFunc=p2.bind(scope); // Create a temporary function with this scope
    return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
    } // pLogInfo

// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
    {
    // Do the final 'then' in the chain: Writes "local info, server info"
    return pWriteFile('log',this.localInfo+','+serverInfo);
    } // p2

Це рішення можна застосувати наступним чином:

pLogInfo("local info").then().catch(err);

(Примітка. Більш складна і повна версія цього рішення була протестована, але не ця прикладна версія, тому вона може мати помилку.)


Це , здається, по тій же схемі , як і в вкладеності (а) CLOSURES відповідь
Берги

Це схоже. З того часу я дізнався, що новий синтаксис Async / Await включає автоматичне прив'язування аргументів, тому всі аргументи доступні для всіх асинхронних функцій. Я відмовляюся від обіцянок.
Девід Спектор

async/ awaitвсе ще означає використовувати обіцянки. Що ви можете відмовитися - це thenдзвінки з зворотними дзвінками.
Берги

-1

Те, що я дізнаюся про обіцянки, - це використовувати його лише як значення, що повертаються, уникати посилань на них, якщо можливо. синтаксис async / очікувати особливо практичний для цього. Сьогодні всі останні браузери та вузли підтримують це: https://caniuse.com/#feat=async-functions - це проста поведінка, і код - це як читання синхронного коду, забудьте про зворотні дзвінки ...

У тих випадках, коли мені потрібно посилатись на обіцянки, це коли створення та вирішення відбуваються в незалежних / не пов'язаних з ними місцях. Тому замість штучної асоціації та, ймовірно, слухача подій просто для вирішення "віддаленої" обіцянки, я вважаю за краще викривати обіцянку як відкладену, що наступний код реалізує її у дійсному es5

/**
 * Promise like object that allows to resolve it promise from outside code. Example:
 *
```
class Api {
  fooReady = new Deferred<Data>()
  private knower() {
    inOtherMoment(data=>{
      this.fooReady.resolve(data)
    })
  }
}
```
 */
var Deferred = /** @class */ (function () {
  function Deferred(callback) {
    var instance = this;
    this.resolve = null;
    this.reject = null;
    this.status = 'pending';
    this.promise = new Promise(function (resolve, reject) {
      instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); };
      instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); };
    });
    if (typeof callback === 'function') {
      callback.call(this, this.resolve, this.reject);
    }
  }
  Deferred.prototype.then = function (resolve) {
    return this.promise.then(resolve);
  };
  Deferred.prototype.catch = function (r) {
    return this.promise.catch(r);
  };
  return Deferred;
}());

перекладено з проекту моєї машинописи:

https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31

Для більш складних випадків я часто використовую ці комунальні послуги з невеликими обіцянками без перевірених та набраних залежностей. p-карта була корисною кілька разів. Я думаю, що він охоплював більшість випадків використання:

https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=


Здається, ви пропонуєте або змінений контекстний стан, або синхронний огляд ?
Берги

@bergi Перший раз я очолюю ці імена. Додавання до списку дякую. Я знаю, що такі обіцянки себе усвідомлюють за іменем Deferred - BTW реалізація - це просто обіцянка з рішучим завершенням. Мені часто потрібна така закономірність у тих випадках, коли відповідальність за створення та вирішення обіцянок є незалежною, тому немає необхідності їх співвідносити лише для вирішення обіцянки. Я адаптувався, але не для вашого прикладу, і використовував клас, але, можливо, еквівалент.
онкоберез
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.