Как преобразовать существующий API обратного вызова в обещания?

721

Я хочу работать с обещаниями, но у меня есть API обратного вызова в таком формате:

1. Загрузка DOM или другое одноразовое событие:

window.onload; // set to callback
...
window.onload = function() {

};

2. Простой обратный вызов:

function request(onChangeHandler) {
    ...
}
request(function() {
    // change happened
    ...
});

3. Обратный вызов стиля узла («nodeback»):

function getStuff(dat, callback) {
    ...
}
getStuff("dataParam", function(err, data) {
    ...
})

4. Целая библиотека с обратными вызовами в стиле узла:

API;
API.one(function(err, data) {
    API.two(function(err, data2) {
        API.three(function(err, data3) {
            ...
        });
    });
});

Как мне работать с API в обещаниях, как его «обещать»?

Бенджамин Грюнбаум
источник
Я опубликовал свой собственный ответ, но ответы на вопросы о том, как это сделать для конкретной библиотеки или в более сложных обстоятельствах, также очень приветствуются.
Бенджамин Грюнбаум
@Bergi Это интересная идея, я попытался дать общий ответ, который использует два общих подхода (конструктор Promise и отложенный объект). Я попытался дать две альтернативы в ответах. Я согласен, что RTFMing решает эту проблему, но мы часто сталкиваемся с этой проблемой и здесь, и в трекере ошибок, поэтому я решил, что существует «канонический вопрос» - я думаю, что RTFMing решает около 50% проблем в теге JS: D Если У вас есть интересное понимание, чтобы внести свой вклад в ответ или отредактировать его, и мы будем очень признательны.
Бенджамин Грюнбаум
Создает ли new Promiseдобавление какие-либо существенные накладные расходы? Я хочу обернуть все мои синхронные функции Noje.js в Promise, чтобы удалить весь синхронный код из моего Node-приложения, но так ли это лучше? Другими словами, функция, которая принимает статический аргумент (например, строку) и возвращает вычисленный результат, я должен обернуть это в обещание? ... Я где-то читал, что у вас не должно быть никакого синхронного кода в Nodejs.
Ронни Ройстон
1
@RonRoyston нет, не стоит переносить синхронные вызовы с обещаниями - только асинхронные вызовы, которые могут выполнять ввод / вывод
Бенджамин Грюнбаум

Ответы:

744

Обещания имеют статус, они начинаются как ожидающие и могут рассчитывать на:

  • выполнив это означает, что вычисление завершено успешно.
  • отклонено означает, что вычисление не удалось.

Функции возврата обещаний никогда не должны выдаваться, вместо этого они должны возвращать отклонения. Функция возврата из обещания заставит вас использовать и a, } catch { и a .catch. Люди, использующие обещанные API-интерфейсы, не ожидают обещаний. Если вы не уверены, как работают асинхронные API в JS - сначала посмотрите этот ответ .

1. Загрузка DOM или другое одноразовое событие:

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

С современными реализациями обещаний, которые поддерживают Promiseконструктор как родные обещания ES6:

function load() {
    return new Promise(function(resolve, reject) {
        window.onload = resolve;
    });
}

Затем вы будете использовать полученное обещание так:

load().then(function() {
    // Do things after onload
});

С библиотеками, которые поддерживают отложенный (давайте используем здесь $ q для этого примера, но позже мы также будем использовать jQuery):

function load() {
    var d = $q.defer();
    window.onload = function() { d.resolve(); };
    return d.promise;
}

Или с помощью jQuery-подобного API, перехватывая событие, происходящее один раз:

function done() {
    var d = $.Deferred();
    $("#myObject").once("click",function() {
        d.resolve();
    });
    return d.promise();
}

2. Простой обратный вызов:

Эти API довольно распространены, так как ... обратные вызовы распространены в JS. Давайте посмотрим на общий случай наличия onSuccessи onFail:

function getUserData(userId, onLoad, onFail) { 

С современными реализациями обещаний, которые поддерживают Promiseконструктор как родные обещания ES6:

function getUserDataAsync(userId) {
    return new Promise(function(resolve, reject) {
        getUserData(userId, resolve, reject);
    });
}

С библиотеками, которые поддерживают отложенный (давайте используем jQuery для этого примера здесь, но мы также использовали $ q выше):

function getUserDataAsync(userId) {
    var d = $.Deferred();
    getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });
    return d.promise();
}

jQuery также предлагает $.Deferred(fn)форму, которая позволяет нам написать выражение, которое очень близко имитирует new Promise(fn)форму, следующим образом:

function getUserDataAsync(userId) {
    return $.Deferred(function(dfrd) {
        getUserData(userId, dfrd.resolve, dfrd.reject);
    }).promise();
}

Примечание: здесь мы используем тот факт, что jQuery deferred resolveи rejectметоды являются «отделяемыми»; то есть. они привязаны к экземпляру jQuery.Deferred (). Не все библиотеки предлагают эту функцию.

3. Обратный вызов стиля узла («nodeback»):

Обратные вызовы стиля узла (nodebacks) имеют определенный формат, где обратные вызовы всегда являются последним аргументом, а его первый параметр является ошибкой. Давайте сначала пообещаем одно вручную:

getStuff("dataParam", function(err, data) { 

Для того, чтобы:

function getStuffAsync(param) {
    return new Promise(function(resolve, reject) {
        getStuff(param, function(err, data) {
            if (err !== null) reject(err);
            else resolve(data);
        });
    });
}

С deferreds вы можете сделать следующее (давайте используем Q для этого примера, хотя Q теперь поддерживает новый синтаксис, который вы должны предпочесть ):

function getStuffAsync(param) {
    var d = Q.defer();
    getStuff(param, function(err, data) {
        if (err !== null) d.reject(err);
        else d.resolve(data);
    });
    return d.promise;   
}

В общем, вам не следует слишком много обещать вручную, в большинстве библиотек обещаний, которые были разработаны с учетом Node, а также в собственных обещаниях в Node 8+ есть встроенный метод для обещания обратных узлов. Например

var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only

4. Целая библиотека с обратными вызовами в стиле узла:

Здесь нет золотого правила, вы обещаете их один за другим. Тем не менее, некоторые реализации обещаний позволяют вам делать это массово, например, в Bluebird, преобразование API обратной ноды в API обещаний так же просто, как:

Promise.promisifyAll(API);

Или с нативными обещаниями в Node :

const { promisify } = require('util');
const promiseAPI = Object.entries(API).map(([key, v]) => ({key, fn: promisify(v)}))
                         .reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});

Ноты:

  • Конечно, когда вы находитесь в .thenобработчике, вам не нужно ничего обещать. Возврат обещания от .thenобработчика разрешит или отклонит значение этого обещания. Бросок из .thenобработчика также является хорошей практикой и отвергнет обещание - это знаменитое обещание безопасности броска.
  • В реальном onloadслучае вы должны использовать, addEventListenerа не onX.
Бенджамин Грюнбаум
источник
Бенджамин, я принял твое приглашение отредактировать и добавил еще один пример jQuery в дело 2. Прежде чем он появится, он должен быть рецензирован. Надеюсь, вам понравится.
Roamer-1888
@ Roamer-1888 это было отклонено, так как я не видел и не принимал это вовремя. Что бы это ни стоило, я не думаю, что дополнение слишком уместно, хотя и полезно.
Бенджамин Грюнбаум
2
Benjamin, независимо от того , или нет , resolve()и reject()написано , чтобы быть многоразовыми, я рискую , что мои предложили изменить это значение , поскольку он предлагает пример JQuery формы $.Deferred(fn), которая в противном случае отсутствует. Если включен только один пример jQuery, то я предлагаю, чтобы он имел такую ​​форму, а не var d = $.Deferred();и т. Д., Поскольку людям следует поощрять использование часто пренебрегаемой $.Deferred(fn)формы, плюс в ответе, подобном этому, он ставит jQuery в один ряд с libs, которые используют шаблон Revealing Constructor .
Roamer-1888
Хех, чтобы быть на 100% справедливым, я не знал, что jQuery позволит вам это сделать $.Deferred(fn), если вы отредактируете это вместо существующего примера в течение следующих 15 минут, я уверен, что могу попытаться утвердить его вовремя :)
Бенджамин Грюнбаум,
7
Это отличный ответ. Вы можете обновить его, упомянув также util.promisify, что Node.js собирается добавить в свое ядро ​​начиная с RC 8.0.0. Его работа не сильно отличается от Bluebird Promise.promisify, но имеет то преимущество, что не требует дополнительных зависимостей, в случае, если вы просто хотите родной Promise. Я написал сообщение в блоге об util.promisify для тех, кто хочет узнать больше на эту тему.
Бруно,
55

Сегодня я могу использовать Promiseв Node.jsкачестве простого метода Javascript.

Простой и простой пример PromiseKISS way):

Простой Javascript Async API-код:

function divisionAPI (number, divider, successCallback, errorCallback) {

    if (divider == 0) {
        return errorCallback( new Error("Division by zero") )
    }

    successCallback( number / divider )

}

Promise Код Javascript Async API:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

        if (divider == 0) {
            return rejected( new Error("Division by zero") )
        }

        fulfilled( number / divider )

     })

}

(Я рекомендую посетить этот прекрасный источник )

Также Promiseможет быть использовано с тусовкой async\awaitв , ES7чтобы сделать ожидание потока программы для fullfiledрезультата , как в следующем:

function getName () {

    return new Promise(function (fulfilled, rejected) {

        var name = "John Doe";

        // wait 3000 milliseconds before calling fulfilled() method
        setTimeout ( 
            function() {
                fulfilled( name )
            }, 
            3000
        )

    })

}


async function foo () {

    var name = await getName(); // awaits for a fulfilled result!

    console.log(name); // the console writes "John Doe" after 3000 milliseconds

}


foo() // calling the foo() method to run the code

Другое использование с тем же кодом с использованием .then()метода

function getName () {

    return new Promise(function (fulfilled, rejected) {

        var name = "John Doe";

        // wait 3000 milliseconds before calling fulfilled() method
        setTimeout ( 
            function() {
                fulfilled( name )
            }, 
            3000
        )

    })

}


// the console writes "John Doe" after 3000 milliseconds
getName().then(function(name){ console.log(name) })

Promiseтакже может использоваться на любой платформе, основанной на Node.js, например react-native.

Бонус : гибридный метод
(предполагается, что метод обратного вызова имеет два параметра как ошибку и результат)

function divisionAPI (number, divider, callback) {

    return new Promise(function (fulfilled, rejected) {

        if (divider == 0) {
            let error = new Error("Division by zero")
            callback && callback( error )
            return rejected( error )
        }

        let result = number / divider
        callback && callback( null, result )
        fulfilled( result )

     })

}

Вышеупомянутый метод может ответить на результат для старого способа обратного вызова и обещаний использования.

Надеюсь это поможет.

efkan
источник
3
Похоже, они не показывают, как перейти к обещаниям.
Дмитрий Зайцев
33

Перед преобразованием функции в качестве обещания в Node.JS

var request = require('request'); //http wrapped module

function requestWrapper(url, callback) {
    request.get(url, function (err, response) {
      if (err) {
        callback(err);
      }else{
        callback(null, response);             
      }      
    })
}


requestWrapper(url, function (err, response) {
    console.log(err, response)
})

После преобразования

var request = require('request');

function requestWrapper(url) {
  return new Promise(function (resolve, reject) { //returning promise
    request.get(url, function (err, response) {
      if (err) {
        reject(err); //promise reject
      }else{
        resolve(response); //promise resolve
      }
    })
  })
}


requestWrapper('http://localhost:8080/promise_request/1').then(function(response){
    console.log(response) //resolve callback(success)
}).catch(function(error){
    console.log(error) //reject callback(failure)
})

Incase вам нужно обработать несколько запросов

var allRequests = [];
allRequests.push(requestWrapper('http://localhost:8080/promise_request/1')) 
allRequests.push(requestWrapper('http://localhost:8080/promise_request/2'))
allRequests.push(requestWrapper('http://localhost:8080/promise_request/5'))    

Promise.all(allRequests).then(function (results) {
  console.log(results);//result will be array which contains each promise response
}).catch(function (err) {
  console.log(err)
});
Сивашанмугам Каннан
источник
23

Я не думаю, что window.onloadпредложение @Benjamin будет работать постоянно, так как оно не определяет, вызывается ли оно после загрузки. Я был укушен этим много раз. Вот версия, которая всегда должна работать:

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);
Лео
источник
1
не должна ли "уже завершенная" ветвь использовать setTimeout(resolve, 0)(или setImmediate, если возможно) ветку , чтобы она вызывалась асинхронно?
Альнитак
5
@Alnitak Синхронный вызов resolveв порядке. thenОбработчики Promise гарантируются асинхронным вызовом платформы независимо от того, resolveвызывается ли синхронно.
Джефф Боуман
15

Node.js 8.0.0 включает новый util.promisify()API, который позволяет обернуть стандартные API стиля обратного вызова Node.js в функцию, которая возвращает Promise. Пример использования util.promisify()показан ниже.

const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);

readFile('/some/file')
  .then((data) => { /** ... **/ })
  .catch((err) => { /** ... **/ });

См. Улучшенная поддержка для обещаний

Джан Марко Герарди
источник
2
Уже есть два ответа, описывающих это, зачем размещать третий?
Бенджамин Грюнбаум
1
Просто потому, что эта версия узла уже выпущена, и я сообщил «официальное» описание функции и ссылку.
Джан Марко Герарди
14

В выпуске кандидата на Node.js 8.0.0 появилась новая утилита util.promisify(я писал об util.promisify ), которая заключает в себе способность обещать любую функцию.

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

const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);

Тогда у вас есть readFileметод, который возвращает нативный Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
Bruno
источник
1
Привет, я (OP) фактически предложил util.promisifyдважды (еще в 2014 году, когда этот вопрос был написан, и несколько месяцев назад - который я настаивал на том, чтобы стать основным членом Node, и это текущая версия, которую мы имеем в Node). Поскольку он еще не является общедоступным - я еще не добавил его к этому ответу. Мы были бы очень признательны за отзывы об использовании и знакомство с некоторыми подводными камнями, чтобы иметь лучшие документы для релиза :)
Бенджамин Грюнбаум
1
Кроме того, вы можете обсудить пользовательский флаг для обещания util.promisifyв своем блоге :)
Бенджамин Грюнбаум
@BenjaminGruenbaum Вы имеете в виду тот факт, что с помощью util.promisify.customсимвола можно переопределить результат util.promisify? Честно говоря, это было преднамеренное промах, потому что я пока не могу найти полезного варианта использования. Возможно, вы можете дать мне некоторые входные данные?
Бруно,
1
Конечно, рассмотрим API-интерфейсы, такие как fs.existsили API-интерфейсы, которые не следуют соглашению Node - синяя птица Promise.promisify может ошибиться, но сделает util.promisifyэто правильно.
Бенджамин Грюнбаум
7

Вы можете использовать собственные обещания JavaScript с Node JS.

Ссылка на код моего облака 9: https://ide.c9.io/adx2803/native-promises-in-node

/**
* Created by dixit-lab on 20/6/16.
*/

var express = require('express');
var request = require('request');   //Simplified HTTP request client.


var app = express();

function promisify(url) {
    return new Promise(function (resolve, reject) {
        request.get(url, function (error, response, body) {
            if (!error && response.statusCode == 200) {
                resolve(body);
            }
            else {
                reject(error);
            }
        })
    });
}

//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
    //get the post with post id 100
    promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
        var obj = JSON.parse(result);
        return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
    })
    .catch(function (e) {
        console.log(e);
    })
    .then(function (result) {
        res.end(result);
    })
})

var server = app.listen(8081, function () {
    var host = server.address().address
    var port = server.address().port

    console.log("Example app listening at http://%s:%s", host, port)
})

//run webservice on browser : http://localhost:8081/listAlbums
Apoorv
источник
7

С простым старым ванильным javaScript вот решение для обещания обратного вызова API.

function get(url, callback) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url);
        xhr.addEventListener('readystatechange', function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    console.log('successful ... should call callback ... ');
                    callback(null, JSON.parse(xhr.responseText));
                } else {
                    console.log('error ... callback with error data ... ');
                    callback(xhr, null);
                }
            }
        });
        xhr.send();
    }

/**
     * @function promisify: convert api based callbacks to promises
     * @description takes in a factory function and promisifies it
     * @params {function} input function to promisify
     * @params {array} an array of inputs to the function to be promisified
     * @return {function} promisified function
     * */
    function promisify(fn) {
        return function () {
            var args = Array.prototype.slice.call(arguments);
            return new Promise(function(resolve, reject) {
                fn.apply(null, args.concat(function (err, result) {
                    if (err) reject(err);
                    else resolve(result);
                }));
            });
        }
    }

var get_promisified = promisify(get);
var promise = get_promisified('some_url');
promise.then(function (data) {
        // corresponds to the resolve function
        console.log('successful operation: ', data);
}, function (error) {
        console.log(error);
});
daviddavis
источник
6

Библиотека Q от kriskowal включает функции обратного вызова к обещанию. Такой метод:

obj.prototype.dosomething(params, cb) {
  ...blah blah...
  cb(error, results);
}

можно конвертировать с помощью Q.ninvoke

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
Джейсон Лавман
источник
1
В каноническом ответе уже упоминается Q.denodeify. Нужно ли выделять библиотечных помощников?
Берги
3
я нашел это полезным, как гугл об обещании в Q приводит здесь
Эд Сайкс
4

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

function callbackToPromise(func){

    return function(){

        // change this to use what ever promise lib you are using
        // In this case i'm using angular $q that I exposed on a util module

        var defered = util.$q.defer();

        var cb = (val) => {
            defered.resolve(val);
        }

        var args = Array.prototype.slice.call(arguments);
        args.push(cb);    
        func.apply(this, args);

        return defered.promise;
    }
}
user1852503
источник
4

Под узлом v7.6 +, который имеет встроенные обещания и асинхронность:

// promisify.js
let promisify = fn => (...args) =>
    new Promise((resolve, reject) =>
        fn(...args, (err, result) => {
            if (err) return reject(err);
            return resolve(result);
        })
    );

module.exports = promisify;

Как пользоваться:

let readdir = require('fs').readdir;
let promisify = require('./promisify');
let readdirP = promisify(readdir);

async function myAsyncFn(path) {
    let entries = await readdirP(path);
    return entries;
}
Пол Сполдинг
источник
3

В Node.js 8 вы можете обещать методы объекта на лету, используя этот модуль npm:

https://www.npmjs.com/package/doasync

Он использует util.promisify и Proxies, чтобы ваши объекты оставались неизменными. Мемоизация также выполняется с использованием WeakMaps). Вот некоторые примеры:

С объектами:

const fs = require('fs');
const doAsync = require('doasync');

doAsync(fs).readFile('package.json', 'utf8')
  .then(result => {
    console.dir(JSON.parse(result), {colors: true});
  });

С функциями:

doAsync(request)('http://www.google.com')
  .then(({body}) => {
    console.log(body);
    // ...
  });

Вы даже можете использовать native callи applyдля привязки некоторого контекста:

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
Do Async
источник
2

Вы можете использовать встроенный Promise в ES6, например, для работы с setTimeout:

enqueue(data) {

    const queue = this;
    // returns the Promise
    return new Promise(function (resolve, reject) {
        setTimeout(()=> {
                queue.source.push(data);
                resolve(queue); //call native resolve when finish
            }
            , 10); // resolve() will be called in 10 ms
    });

}

В этом примере Обещание не имеет причин для отказа, поэтому reject()никогда не вызывается.

Николас Зозол
источник
2

Функция стиля обратного вызова всегда такая (почти все функции в node.js - это стиль):

//fs.readdir(path[, options], callback)
fs.readdir('mypath',(err,files)=>console.log(files))

Этот стиль имеет ту же особенность:

  1. функция обратного вызова передается последним аргументом.

  2. функция обратного вызова всегда принимает объект ошибки в качестве первого аргумента.

Таким образом, вы можете написать функцию для преобразования функции с этим стилем следующим образом:

const R =require('ramda')

/**
 * A convenient function for handle error in callback function.
 * Accept two function res(resolve) and rej(reject) ,
 * return a wrap function that accept a list arguments,
 * the first argument as error, if error is null,
 * the res function will call,else the rej function.
 * @param {function} res the function which will call when no error throw
 * @param {function} rej the function which will call when  error occur
 * @return {function} return a function that accept a list arguments,
 * the first argument as error, if error is null, the res function
 * will call,else the rej function
 **/
const checkErr = (res, rej) => (err, ...data) => R.ifElse(
    R.propEq('err', null),
    R.compose(
        res,
        R.prop('data')
    ),
    R.compose(
        rej,
        R.prop('err')
    )
)({err, data})

/**
 * wrap the callback style function to Promise style function,
 * the callback style function must restrict by convention:
 * 1. the function must put the callback function where the last of arguments,
 * such as (arg1,arg2,arg3,arg...,callback)
 * 2. the callback function must call as callback(err,arg1,arg2,arg...)
 * @param {function} fun the callback style function to transform
 * @return {function} return the new function that will return a Promise,
 * while the origin function throw a error, the Promise will be Promise.reject(error),
 * while the origin function work fine, the Promise will be Promise.resolve(args: array),
 * the args is which callback function accept
 * */
 const toPromise = (fun) => (...args) => new Promise(
    (res, rej) => R.apply(
        fun,
        R.append(
            checkErr(res, rej),
            args
        )
    )
)

Для краткости в приведенном выше примере используется ramda.js. Ramda.js - отличная библиотека для функционального программирования. В приведенном выше коде мы использовали его apply (как javascript function.prototype.apply) и append (как javascript function.prototype.push). Итак, теперь мы можем преобразовать функцию стиля обратного вызова в функцию обещания стиля:

const {readdir} = require('fs')
const readdirP = toPromise(readdir)
readdir(Path)
    .then(
        (files) => console.log(files),
        (err) => console.log(err)
    )

Функции toPromise и checkErr принадлежат библиотеке berserk , это функциональная вилка библиотеки программирования от ramda.js ( созданная мной).

Надеюсь, что этот ответ полезен для вас.

jituanlin
источник
2

Вы можете сделать что-то вроде этого

// @flow

const toPromise = (f: (any) => void) => {
  return new Promise<any>((resolve, reject) => {
    try {
      f((result) => {
        resolve(result)
      })
    } catch (e) {
      reject(e)
    }
  })
}

export default toPromise

Тогда используйте это

async loadData() {
  const friends = await toPromise(FriendsManager.loadFriends)

  console.log(friends)
}
onmyway133
источник
2
Эй, я не уверен, что это добавляет к существующим ответам (возможно, уточнить?). Кроме того, нет необходимости в try / catch внутри конструктора обещаний (он делает это автоматически для вас). Также неясно, для каких функций это работает (которые вызывают обратный вызов с одним аргументом об успехе? Как обрабатываются ошибки?)
Бенджамин Грюнбаум
1

es6-promisify преобразует функции на основе обратного вызова в функции на основе Promise.

const promisify = require('es6-promisify');

const promisedFn = promisify(callbackedFn, args);

Ссылка: https://www.npmjs.com/package/es6-promisify

Пуджан Шривастава
источник
1

Моя многообещающая версия callbackфункции - это Pфункция:

var P = function() {
  var self = this;
  var method = arguments[0];
  var params = Array.prototype.slice.call(arguments, 1);
  return new Promise((resolve, reject) => {
    if (method && typeof(method) == 'function') {
      params.push(function(err, state) {
        if (!err) return resolve(state)
        else return reject(err);
      });
      method.apply(self, params);
    } else return reject(new Error('not a function'));
  });
}
var callback = function(par, callback) {
  var rnd = Math.floor(Math.random() * 2) + 1;
  return rnd > 1 ? callback(null, par) : callback(new Error("trap"));
}

callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))

P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))

PФункция требует обратного вызова подпись должна быть callback(error,result).

loretoparisi
источник
1
Какое преимущество это имеет над нативным обещанием или над ответами выше?
Бенджамин Грюнбаум
Что вы имеете в виду для родного обещания?
Лоретопариси
util.promisify(fn)
Бенджамин Грюнбаум
ах да конечно :). Как раз и пример, чтобы показать основную идею. На самом деле вы можете видеть, как даже нативный требует, чтобы сигнатура функции была определена как, (err, value) => ...или вы должны определить собственную (см. Пользовательские обещанные функции). Спасибо, хороший кальян.
Loretoparisi
1
@loretoparisi FYI, var P = function (fn, ...args) { return new Promise((resolve, reject) => fn.call(this, ...args, (error, result) => error ? reject(error) : resolve(result))); };сделал бы то же самое, что и вы, и это намного проще.
Патрик Робертс
1

Ниже приведена реализация того, как функция (API обратного вызова) может быть преобразована в обещание.

function promisify(functionToExec) {
  return function() {
    var array = Object.values(arguments);
    return new Promise((resolve, reject) => {
      array.push(resolve)
      try {
         functionToExec.apply(null, array);
      } catch (error) {
         reject(error)
      }
    })
  }
}

// USE SCENARIO

function apiFunction (path, callback) { // Not a promise
  // Logic
}

var promisedFunction = promisify(apiFunction);

promisedFunction('path').then(()=>{
  // Receive the result here (callback)
})

// Or use it with await like this
let result = await promisedFunction('path');
Mzndako
источник
-2

Это на 5 лет позже, но я хотел опубликовать здесь мою версию с промыслами, которая берет функции из API обратных вызовов и превращает их в обещания

const promesify = fn => {
  return (...params) => ({
    then: cbThen => ({
      catch: cbCatch => {
        fn(...params, cbThen, cbCatch);
      }
    })
  });
};

Взгляните на эту очень простую версию здесь: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a

Джулиан Торрегроса
источник
1
Это не обещание, оно не
Бенджамин Грюнбаум