Как получить доступ к результатам предыдущих обещаний в цепочке .then()?

Я изменил свой код на promises и создал замечательную длинную цепочку обещаний, состоящую из нескольких обратных вызовов .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?
    });
}

Ответ 1

Гармония ECMAScript

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

ECMAScript 8

Вам больше не нужна функция вызова или обратного вызова then, так как в асинхронной функции (которая возвращает обещание при вызове) вы можете просто ждать, пока promises будет разрешаться напрямую. Он также имеет произвольные структуры управления, такие как условия, циклы и пункты try-catch, но для удобства они нам не нужны:

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, when,...), которые делают эту асинхронную пошаговое выполнение для вас, когда вы даете им функцию генератора, которая дает promises.

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 на generators и асинхронные функции.

И тогда есть также много других языков компиляции к JS которые предназначены для ослабления асинхронного программирования. Обычно они используют синтаксис, похожий на await (например, Iced CoffeeScript), но есть и другие, которые имеют Haskell-подобные do -notation (например, LatteJs, monadic, PureScript или LispyScript).

Ответ 2

Разрыв цепи

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

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

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, when,...): .spread(function(resultA, resultB) { ….

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

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

Ответ 3

Синхронный контроль

Назначение переменных promises -for-later-needed-values, а затем получение их значения посредством синхронного контроля. В примере используется метод bluebird .value(), но многие библиотеки предоставляют аналогичный метод.

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();
    });
}

Ответ 4

Закрытие (и) закрытия

Использование закрытий для поддержки области переменных (в нашем случае, параметров функции обратного вызова успеха) является естественным решением JavaScript. С promises мы можем произвольно вставлять и сглаживать .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 from Underscore/lodash или native .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

Явный проход

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

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() был pro & shy; vi & shy; выдан многими библиотеками обещаний (Q, Bluebird, when,...), Он выполняет функцию с несколькими параметрами - по одному для каждого элемента массива - для использования как .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
    });
}

Хотя этот шаблон гарантирует, что плоская цепочка и явные объекты состояния могут улучшить ясность, он станет утомительным для длинной цепочки. Особенно, когда вам нужно государство только спорадически, вам все равно нужно пройти его через каждый шаг. С помощью этого фиксированного интерфейса одиночные обратные вызовы в цепочке довольно жестко связаны и негибкие для изменения. Это делает факторинг более сложными, а обратные вызовы не могут поставляться напрямую из других модулей - их всегда нужно обернуть в шаблонный код, который заботится о состоянии. Абстрактные вспомогательные функции, подобные приведенным выше, могут немного облегчить боль, но она всегда будет присутствовать.

Ответ 6

Мутируемое контекстуальное состояние

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

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));
}

Ответ 7

Менее резкое вращение "изменяемого контекстуального состояния"

Использование объекта с локальной областью для сбора промежуточных результатов в цепочке обещаний - разумный подход к поставленному вами вопросу. Рассмотрим следующий фрагмент:

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.

Ответ 8

Node 7.4 теперь поддерживает вызовы async/wait с флагом гармонии.

Попробуйте следующее:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

и запустите файл с помощью

node --harmony-async-await getExample.js

Просто, как может быть!

Ответ 9

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

В соответствии с обещаниями о том, как цепочка 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);
    });

Ответ 10

Другой ответ, используя babel-node version < 6

Используя async - await

npm install -g [email protected]

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Затем запустите babel-node example.js и voila!

Ответ 11

Я не буду использовать этот шаблон в своем собственном коде, так как я не большой поклонник использования глобальных переменных. Тем не менее, он будет работать.

Пользователь является многообещающей моделью Mongoose.

var globalVar = '';

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

Ответ 12

Другой ответ, используя последовательный исполнитель 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>

Ответ 13

При использовании 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

Ответ 14

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

Ответ 15

Я думаю, вы можете использовать хэш 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);
    });

Ответ 16

Решение:

Вы можете поместить промежуточные значения в область видимости в любой более поздней функции 'then' явно, используя 'bind'. Это хорошее решение, которое не требует изменения того, как работает Promises, и требует только одну или две строки кода для распространения значений так же, как ошибки уже распространены.

Вот полный пример:

// 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);

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

Ответ 17

Код очень хорош, когда у нас есть цепочка, как показано ниже:

  • Каждая функция вызывает другую функцию, передает ее на нее, она продолжает выполнение, пока не достигнет функции done().
  • Если одна из функциональных цепочек терпит неудачу, то она останавливается и переходит к doneWithError
  • Каждая функция имеет все предыдущие выходные значения в массиве 'input', из-за операции .push, имя вводится, поскольку оно может быть введено для следующей функции.
  • .push требуется только в том случае, если вам нужны все предыдущие значения, иначе просто последнее значение будет достаточно хорошим для отправки, например: resolve ({func2: 'func2 good'}).

Теперь код:

'use strict';

//вызов основной функции

doMain();

//главная функция

function doMain() {

    let input = [];
    func1(input)
            .then(func2)
            .then(func3)
            .then(done)
            .catch(done_with_error);
}

//индивидуальная функция 1

function func1(input) {
    console.log('func1 - begin');

    console.log('func1 - new Promise - before');
    return new Promise((resolve, reject) => {
        if (!input) {
            console.log('func1 - catch');
            return reject('func1 error');
        }

        console.log('func1 - then');

        input.push({func1: "func1 good"});

        return resolve(input);
    });
}

//индивидуальная функция 2

function func2(input) {
    console.log('func2 - begin');

    console.log('func2 - new Promise - before');
    return new Promise((resolve, reject) => {
        if (!input) {
            console.log('func2 - catch');
            return reject('func2 error');
        }

        console.log('func2 - then');

        input.push({func2: 'func2 good'});

        return resolve(input);
    });
}

//индивидуальная функция 3

function func3(input) {
    console.log('func3 - begin');

    console.log('func3 - new Promise - before');
    return new Promise((resolve, reject) => {
        if (!input) {
            console.log('func3 - catch');
            return reject('func3 error');
        }

        console.log('func3 - then');

        // throw new Error('err');

        input.push({func3: 'func3 good'});

        return resolve(input);
    });
}

//выполняются все отдельные функции - успех

function done(input) {
    console.log('done - begin');
    console.log(input);
}

//сбой одной отдельной функции

function doneWithError(err) {
    console.log('doneWithError - begin');
    console.log(err);
}

//вывод

//    func1 - begin
//    func1 - new Promise - before
//    func1 - then
//    func2 - begin
//    func2 - new Promise - before
//    func2 - then
//    func3 - begin
//    func3 - new Promise - before
//    func3 - then
//    done - begin
//    [
//      { func1: 'func1 good' },
//      { func2: 'func2 good' },
//      { func3: 'func3 good' }
//    ]
//

Ответ 18

Что я узнаю об обещаниях, так это использовать их только в качестве возвращаемых значений , избегая ссылки на них, если это возможно. Синтаксис async/await особенно полезен для этого. Сегодня все последние браузеры и узлы поддерживают его: 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=