Como converter uma API de retorno de chamada existente em promessas?

721

Quero trabalhar com promessas, mas tenho uma API de retorno de chamada em um formato como:

1. Carregamento do DOM ou outro evento único:

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

};

2. Retorno de chamada simples:

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

3. Retorno de chamada no estilo do nó ("nodeback"):

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

4. Uma biblioteca inteira com retornos de chamada no estilo do nó:

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

Como trabalho com a API em promessas, como "prometo" isso?

Benjamin Gruenbaum
fonte
Postei minha própria resposta, mas as respostas expandindo sobre como fazer isso para uma biblioteca específica ou em mais circunstâncias, e as edições também são muito bem-vindas.
Benjamin Gruenbaum
@ Bergi Essa é uma ideia interessante, tentei fazer uma resposta geral que use as duas abordagens comuns (construtor Promise e objeto adiado). Eu tentei dar as duas alternativas em respostas. Concordo que o RTFMing resolve esse problema, mas geralmente o encontramos aqui e no rastreador de bugs, então achei que havia uma 'pergunta canônica' - acho que o RTFMing resolve cerca de 50% dos problemas na tag JS: D Se você tem um insight interessante para contribuir com uma resposta ou editar que seria muito apreciado.
Benjamin Gruenbaum
Criar um new Promiseacréscimo sobrecarga significativa? Estou querendo agrupar todas as minhas funções síncronas de Noje.js. em uma promessa, para remover todo o código síncrono do meu aplicativo Node, mas essa é uma prática recomendada? Em outras palavras, uma função que aceita um argumento estático (por exemplo, uma seqüência de caracteres) e retorna um resultado calculado, devo enviá-lo em uma promessa? ... Li em algum lugar que você não deve ter nenhum código síncrono no Nodejs.
Ronnie Royston
1
@RonRoyston não, não é uma boa idéia para embrulhar chamadas síncronas com promessas - apenas chamadas assíncronas que podem executar I / O
Benjamin Gruenbaum

Respostas:

744

As promessas têm estado, elas começam como pendentes e podem resolver:

  • realizada o que significa que o cálculo foi concluído com êxito.
  • rejeitado o que significa que a computação falhou.

As funções de retorno promissor nunca devem ser lançadas ; elas devem retornar rejeições. O lançamento de uma função de retorno de promessa forçará você a usar os modos a } catch { e a .catch. As pessoas que usam APIs prometidas não esperam que as promessas sejam lançadas. Se você não tem certeza de como as APIs assíncronas funcionam em JS - consulte esta resposta primeiro.

1. Carregamento do DOM ou outro evento único:

Portanto, criar promessas geralmente significa especificar quando elas se estabelecem - ou seja, quando elas passam para a fase cumprida ou rejeitada para indicar que os dados estão disponíveis (e podem ser acessados ​​com .then).

Com implementações de promessa modernas que suportam o Promiseconstrutor como as promessas nativas do ES6:

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

Você usaria a promessa resultante da seguinte maneira:

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

Com bibliotecas que suportam adiado (Vamos usar $ q para este exemplo aqui, mas também usaremos o jQuery posteriormente):

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

Ou com uma jQuery como API, conectando-se a um evento que acontece uma vez:

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

2. Retorno de chamada simples:

Essas APIs são bastante comuns, pois bem ... retornos de chamada são comuns em JS. Vejamos o caso comum de ter onSuccesse onFail:

function getUserData(userId, onLoad, onFail) { 

Com implementações de promessa modernas que suportam o Promiseconstrutor como as promessas nativas do ES6:

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

Com bibliotecas que suportam adiado (Vamos usar jQuery para este exemplo aqui, mas também usamos $ q acima):

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

O jQuery também oferece um $.Deferred(fn)formulário, que tem a vantagem de nos permitir escrever uma expressão que emula muito de perto o new Promise(fn)formulário, da seguinte maneira:

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

Nota: Aqui exploramos o fato de que os métodos resolvee rejectmétodos adiados por jQuery são "destacáveis"; ie eles estão ligados à instância de um jQuery.Deferred (). Nem todas as bibliotecas oferecem esse recurso.

3. Retorno de chamada no estilo do nó ("nodeback"):

Os retornos de chamada no estilo do nó (nodebacks) têm um formato específico em que os retornos de chamada são sempre o último argumento e seu primeiro parâmetro é um erro. Vamos primeiro promisificar um manualmente:

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

Para:

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

Com adiados, você pode fazer o seguinte (vamos usar Q neste exemplo, embora Q agora suporte a nova sintaxe que você preferir ):

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

Em geral, você não deve prometer demais as coisas manualmente, a maioria das bibliotecas promissoras que foram projetadas com o Node em mente, assim como as promessas nativas no Nó 8+, têm um método interno para prometer nodebacks. Por exemplo

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

4. Uma biblioteca inteira com retornos de chamada no estilo do nó:

Não há regra de ouro aqui, você as promete uma a uma. No entanto, algumas implementações promissoras permitem fazer isso em massa, por exemplo, no Bluebird, converter uma API nodeback em uma API promissora é tão simples quanto:

Promise.promisifyAll(API);

Ou com promessas nativas no 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}), {});

Notas:

  • Obviamente, quando você está em um .thenmanipulador, não precisa promisificar as coisas. O retorno de uma promessa de um .thenmanipulador resolverá ou rejeitará o valor dessa promessa. Jogando de um.then manipulador também é uma boa prática e rejeitará a promessa - esta é a famosa promessa de lance de segurança.
  • Em um onloadcaso real , você deve usar em addEventListenervez de onX.
Benjamin Gruenbaum
fonte
Benjamin, aceitei seu convite para editar e adicionei mais um exemplo de jQuery ao caso 2. Ele precisará de revisão por pares antes que apareça. Espero que você goste.
Roamer-1888
@ Roamer-1888, foi rejeitado porque eu não vi e aceitei a tempo. Pelo que vale a pena, não acho que a adição seja relevante demais, embora útil.
Benjamin Gruenbaum
2
Benjamin, ou não resolve()e reject()são escritos para serem reutilizáveis, atrevo-me que a minha edição sugerida é relevante porque oferece um exemplo jQuery da forma $.Deferred(fn), o que está faltando de outra maneira. Se apenas um exemplo do jQuery for incluído, sugiro que seja desse formato e não var d = $.Deferred();etc., pois as pessoas devem ser encorajadas a usar o $.Deferred(fn)formulário muitas vezes negligenciado , além disso, em uma resposta como essa, coloca o jQuery mais parecido com o bibliotecas que usam o padrão revelador do construtor .
Roamer-1888
Heh, para ser 100% justo, eu não sabia que o jQuery deixou você fazer $.Deferred(fn), se você editar isso em vez do exemplo existente nos próximos 15 minutos, tenho certeza de que posso tentar aprová-lo a tempo :) :)
Benjamin Gruenbaum
7
Esta é uma ótima resposta. Convém atualizá-lo mencionando também util.promisifyque o Node.js adicionará ao seu núcleo a partir do RC 8.0.0. Seu funcionamento não é muito diferente do Bluebird Promise.promisify, mas tem a vantagem de não exigir dependências adicionais, caso você queira apenas o Promise nativo. Eu escrevi um post sobre util.promisify para quem quiser ler mais sobre o assunto.
Bruno
55

Hoje, eu posso usar Promiseno Node.jscomo um método Javascript simples.

Um exemplo simples e básico de Promise(com o KISS ):

Código da API Javascript assíncrona simples :

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

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

    successCallback( number / divider )

}

Promise Código da API Javascript Assíncrona:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}

(Eu recomendo visitar esta bela fonte )

Também Promisepode ser usado junto com o async\awaitin ES7para fazer com que o fluxo do programa aguarde um fullfiledresultado como o seguinte:

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

Outro uso com o mesmo código usando o .then()método

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

PromiseTambém pode ser usado em qualquer plataforma que é baseado em Node.js como react-native.

Bônus : um método híbrido
(presume-se que o método de retorno de chamada tenha dois parâmetros como erro e resultado)

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 )

     })

}

O método acima pode responder ao resultado de retorno de chamada à moda antiga e ao uso do Promise.

Espero que isto ajude.

efkan
fonte
3
Estes não parecem mostrar como converter em promessas.
Dmitri Zaitsev
33

Antes de converter uma função como promessa no 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)
})

Depois de convertê-lo

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

Caso você precise lidar com várias solicitações

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)
});
Sivashanmugam Kannan
fonte
23

Não acho que a window.onloadsugestão do @Benjamin funcione o tempo todo, pois não detecta se é chamada após o carregamento. Fui mordido por isso muitas vezes. Aqui está uma versão que sempre deve funcionar:

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);
Leo
fonte
1
o ramo "já concluído" não deve usar setTimeout(resolve, 0)(ou setImmediate, se disponível) para garantir que seja chamado de forma assíncrona?
Alnitak
5
@Alnitak Ligar de forma resolvesíncrona é bom. Os thenmanipuladores da Promise são garantidos pela estrutura para serem chamados de forma assíncrona , independentemente de serem resolvechamados de forma síncrona.
22616 Jeff Bowman
15

O Node.js. 8.0.0 inclui uma nova util.promisify()API que permite que as APIs padrão do estilo de retorno de chamada do Node.j sejam agrupadas em uma função que retorna uma Promessa. Um exemplo de uso de util.promisify()é mostrado abaixo.

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

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

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

Consulte Suporte aprimorado para promessas

Gian Marco Gherardi
fonte
2
Já existem duas respostas descrevendo isso, por que postar uma terceira?
Benjamin Gruenbaum
1
Só porque essa versão do nó foi lançada agora, e eu relatei a descrição e o link "oficiais" do recurso.
Gian Marco Gherardi
14

No candidato a lançamento do Node.js. 8.0.0, há um novo utilitário util.promisify(que escrevi sobre util.promisify ), que encapsula a capacidade de prometer qualquer função.

Não é muito diferente das abordagens sugeridas nas outras respostas, mas tem a vantagem de ser um método central e não exigir dependências adicionais.

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

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

Então você tem um readFilemétodo que retorna um nativo Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
Bruno
fonte
1
Ei, eu (OP) na verdade sugeri util.promisifyduas vezes (em 2014, quando essa pergunta foi escrita, e há alguns meses atrás - que eu insisti como membro principal do Node e é a versão atual que temos no Node). Como ainda não está disponível ao público - ainda não o adicionei a esta resposta. Gostaríamos profundamente Agradecemos o feedback uso embora e conhecer o que algumas armadilhas são, a fim de ter melhores docs para o lançamento :)
Benjamin Gruenbaum
1
Além disso, você pode querer discutir a bandeira personalizada para promisifying com util.promisifyem seu blog :)
Benjamin Gruenbaum
@BenjaminGruenbaum Você quer dizer que, usando o util.promisify.customsímbolo, é possível substituir o resultado do util.promisify? Para ser sincero, foi uma falta intencional, porque ainda não consegui encontrar um caso de uso útil. Talvez você possa me dar algumas informações?
Bruno
1
Claro, considere APIs como fs.existsou APIs que não seguem a convenção do Nó - um pássaro azul Promise.promisify as interpretaria mal, mas as interpretaria util.promisifycorretamente.
Benjamin Gruenbaum
7

Você pode usar promessas nativas do JavaScript com o Nó JS.

Link de código do My Cloud 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
fonte
7

Com o simples javaScript de baunilha antigo, aqui está uma solução para promisificar um retorno de chamada da 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
fonte
6

A biblioteca Q da kriskowal inclui funções de retorno de chamada para promessa. Um método como este:

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

pode ser convertido com Q.ninvoke

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
Jason Loveman
fonte
1
A resposta canônica já menciona Q.denodeify. Precisamos enfatizar os auxiliares da biblioteca?
Bergi
3
eu encontrei este útil como um google sobre promisifying em Q leva aqui
Ed Sykes
4

Quando você tem algumas funções que recebem um retorno de chamada e deseja que elas retornem uma promessa, você pode usar essa função para fazer a conversão.

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
fonte
4

Sob o nó v7.6 +, que possui promessas e assíncronas:

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

Como usar:

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

async function myAsyncFn(path) {
    let entries = await readdirP(path);
    return entries;
}
Paul Spaulding
fonte
3

No Node.js 8, você pode prometer métodos de objeto em tempo real usando este módulo npm:

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

Ele usa util.promisify e Proxies para que seus objetos permaneçam inalterados. A memorização também é feita com o uso de WeakMaps). aqui estão alguns exemplos:

Com objetos:

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

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

Com funções:

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

Você pode até usar nativo calle applyvincular algum contexto:

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
Do Async
fonte
2

Você pode usar o Promise nativo no ES6, por exemplo, para lidar com 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
    });

}

Neste exemplo, a Promessa não tem motivos para falhar, portanto reject()nunca é chamada.

Nicolas Zozol
fonte
2

A função de estilo de retorno de chamada sempre é assim (quase todas as funções no node.js são desse estilo):

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

Esse estilo tem o mesmo recurso:

  1. a função de retorno de chamada é passada pelo último argumento.

  2. a função de retorno de chamada sempre aceita o objeto de erro como seu primeiro argumento.

Portanto, você pode escrever uma função para converter uma função com este estilo assim:

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

Para ser mais conciso, o exemplo acima usou o ramda.js. O Ramda.js é uma excelente biblioteca para programação funcional. No código acima, usamos o método apply (como javascript function.prototype.apply) e anexado (como javascript function.prototype.push). Portanto, podemos converter a função de estilo de retorno de chamada para prometer a função de estilo agora:

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

A função toPromise e checkErr é propriedade da biblioteca berserk , é uma bifurcação da biblioteca de programação funcional por ramda.js (criada por mim).

Espero que esta resposta seja útil para você.

jituanlin
fonte
2

Você pode fazer algo assim

// @flow

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

export default toPromise

Então use

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

  console.log(friends)
}
onmyway133
fonte
2
Ei, não tenho certeza do que isso adiciona às respostas existentes (talvez esclarecer?). Além disso, não há necessidade de try / catch dentro do construtor de promessas (ele faz isso automaticamente para você). Também não está claro o que funciona isso funciona para (que chamam a chamada de retorno com um único argumento em caso de sucesso Como são erros tratada?)
Benjamin Gruenbaum
1

es6-promisify converte funções baseadas em retorno de chamada em funções baseadas em Promise.

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

const promisedFn = promisify(callbackedFn, args);

Ref: https://www.npmjs.com/package/es6-promisify

Pujan Srivastava
fonte
1

Minha versão promisify de uma callbackfunção é a Pfunção:

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

A Pfunção requer que a assinatura de retorno de chamada seja callback(error,result).

Loretoparisi
fonte
1
Que vantagem isso tem sobre a promessa nativa ou sobre as respostas acima?
Benjamin Gruenbaum
O que você quer dizer com promisify nativo?
Loretoparisi
util.promisify(fn)
Benjamin Gruenbaum
ah sim claro :). Apenas e exemplo para mostrar a ideia básica. De fato, você pode ver como até o nativo exige que a assinatura da função seja definida como algo (err, value) => ...ou você deve definir uma customizada (consulte Funções prometidas personalizadas). Obrigado boa catcha.
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))); };faria a mesma coisa que a sua e é muito mais simples.
Patrick Roberts
1

Abaixo está a implementação de como uma função (API de retorno de chamada) pode ser convertida em uma promessa.

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
fonte
-2

É um atraso de 5 anos, mas eu queria postar aqui minha versão promesify, que pega funções da API de retorno de chamada e as transforma em promessas

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

Dê uma olhada nesta versão muito simples aqui: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a

Julian Torregrosa
fonte
1
Isso não é uma promessa, não cadeia, lidar com erros jogado no retorno de chamada ou aceitar um segundo parâmetro em seguida ...
Benjamin Gruenbaum