Como converter uma API de callback existente para promessas?

quero trabalhar com promessas, mas tenho uma API de retorno num formato como:

1. Carga de DOM ou outro evento pontual:

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

};

2. Resposta simples:

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

3. Callback do estilo do nó ("nodeback"):

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

4. Uma biblioteca inteira com o estilo do nó callbacks:

API;
API.one(function(err, data) {
    API.two(function(err, data2) {
        API.three(function(err, data3) {
            ...
        });
    });
});
Como é que eu trabalho com a API em promessas, como é que a "promisifico"?
Author: onmyway133, 2014-03-20

18 answers

As promessas têm estado, começam como pendentes e podem chegar a:

  • cumpriu o que significa que o cálculo foi concluído com sucesso.
  • rejeitou o que significa que o cálculo falhou.
Promete que vais voltar a funções. nunca deve atirar, em vez disso, devem devolver as rejeições. Lançar de uma função de retorno de promessa vai forçá-lo a usar tanto um } catch { e a .catch. Pessoas que usam as API promisificadas não esperam promessas para lançar. Se você não tem certeza de como Async APIs funciona em JS - por favor veja esta resposta primeiro.

1. Carga de DOM ou outro evento pontual:

Então, criar promessas geralmente significa especificar quando elas se estabelecem - isso significa quando elas se movem para a fase cumprida ou rejeitada para indicar que os dados estão disponíveis (e podem ser acessados com .then). Com implementações modernas de promessas que suportam o construtor como um nativo ES6 promises:
function load() {
    return new Promise(function(resolve, reject) {
        window.onload = resolve;
    });
}

Você então usaria a promessa resultante assim:

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

Com bibliotecas que suportam adiadas (vamos usar $ q para este exemplo aqui, mas também vamos usar jQuery mais tarde):

function load() {
    var d = $q.defer();
    window.onload = function() { d.resolve(); };
    return d.promise;
}
Ou com um jQuery como o API, a engatar um evento que acontece uma vez.
function done() {
    var d = $.Deferred();
    $("#myObject").once("click",function() {
        d.resolve();
    });
    return d.promise();
}

2. Resposta simples:

Estas APIs são bastante comuns, uma vez que bem ... os callbacks são comuns no JS. Vamos olhar para o caso comum de ter onSuccess e onFail:
function getUserData(userId, onLoad, onFail) { …
Com implementações modernas de promessas que suportam o construtor como as promessas nativas do ES6:
function getUserDataAsync(userId) {
    return new Promise(function(resolve, reject) {
        getUserData(userId, resolve, reject);
    });
}

Com bibliotecas que suportam adiadas (vamos usar o 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();
}

JQuery também oferece uma forma $.Deferred(fn), que tem a vantagem de nos permitir escrever uma expressão que emula muito de perto a forma new Promise(fn), como se segue:

function getUserDataAsync(userId) {
    return $.Deferred(function(dfrd) {
        getUserData(userId, dfrd.resolve, dfrd.reject);
    }).promise();
}
Nota: aqui exploramos o fato de que um jQuery os métodos de deferred's resolve e reject são "destacáveis"; ou seja. eles estão ligados à instânciade um jQuery.Diferido(). Nem todos os libs oferecem este recurso.

3. Callback do estilo do nó ("nodeback"):

Callbacks estilo de nó (nodebacks) têm um formato particular onde os callbacks são sempre o último argumento e seu primeiro parâmetro é um erro. Vamos primeiro promisificar um manualmente:

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

To:

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

Com deferreds pode fazer o seguinte (vamos usar Q para este exemplo, embora Q agora suporte a nova sintaxe que você deve 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 promisificar as coisas manualmente muito, a maioria das bibliotecas prometedoras que foram projetadas com Node em mente, bem como as promessas nativas no Node 8+ têm um método construído para promisificar 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 o estilo do nó callbacks:

Não há nenhuma regra de ouro aqui, promisiona-os um a um. No entanto, alguns implementações de promessas permitem que você faça isso em massa, por exemplo em Bluebird, converter uma API de nodeback para uma API promessa é tão simples como:
Promise.promisifyAll(API);

Ou com promessas nativas no nó :

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

Notas:

    É claro que, quando se está num controlador, não é preciso promisificar as coisas. Devolver uma promessa de um controlador .then irá resolver ou rejeitar com o valor dessa promessa. Atirar de um manipulador .then também é uma boa prática e rejeitará a promessa-esta é a famosa promessa arremessar a segurança.
  • num caso real de onload, deve usar addEventListener em vez de onX.
 576
Author: Benjamin Gruenbaum, 2018-09-20 13:41:56

Hoje, posso usar Promise em Node.js como um método de Javascript simples.

Um exemplo simples e básico de Promise (com beijo caminho):

Plain Javascript async API code:

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

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

    successCallback( number / divider )

}

Promise código API de Javascript Async:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}
Recomendo visitar esta bela fonte.)

Também Promise pode ser usado em conjunto async\await em ES7 para fazer o programa fluir esperar por um resultado fullfiled 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

Outra utilização com o mesmo código, utilizando o método .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 também pode ser usado em qualquer plataforma que é baseada no nó.js like react-native.

Espero que isto ajude.
 33
Author: efkan, 2018-03-10 07:23:17

Antes de converter uma função como promessa no nó.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');
var Promise = require('bluebird');

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 necessite de lidar com pedidos múltiplos

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)
});
 21
Author: Sivashanmugam Kannan, 2018-06-12 21:34:27

Eu não acho que a sugestão de window.onload por @Benjamin vai funcionar o tempo todo, pois não detecta se é chamada depois da carga. Já fui mordido por isso muitas vezes. Aqui está uma versão que deve sempre funcionar:

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);
 18
Author: Leo, 2015-03-03 12:32:12

Em candidato à libertação para o nó.js 8.0.0, há um novo utilitário, util.promisify (eu escrevi sobre util.promisify , que encapsula a capacidade de promisificar 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 requerendo dependências adicionais.

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

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

Então você tem um método readFile que devolve um nativo Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
 10
Author: Bruno, 2017-05-16 05:35:57

Pode usar as promessas nativas do JavaScript com o nó JS.

A minha ligação de código 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
 5
Author: Apoorv, 2016-06-20 14:35:27

Nó.o js 8.0.0 inclui uma nova API util.promisify() que permite o nó padrão.a APIs do estilo de callback js deve ser embrulhada numa função que devolve 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) => { /** ... **/ });

Ver melhorar o apoio às promessas

 5
Author: Gian Marco Gherardi, 2017-05-31 06:46:50

A Biblioteca Q de kriskowal inclui funções de callback-to-promise. 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) {
});
 3
Author: Jason Loveman, 2015-04-07 18:30:14

Sob o nó v7. 6+ que construiu em promessas e async:

// 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 Utilizar:

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
Author: Paul Spaulding, 2017-04-12 16:48:09

Quando você tem algumas funções que tomam um callback e você quer que eles retornem uma promessa em vez disso você pode usar esta 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;
    }
}
 2
Author: user1852503, 2016-08-25 00:54:47
Com vanilla Javascript, aqui está uma solução para promisificar uma chamada de 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);
});
 2
Author: daviddavis, 2016-11-29 14:44:49

No Nó.js 8 you can promisify object methods on the fly using this npm module:

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

Usa util.promisify e Proxies {[[8]} para que os seus objectos permaneçam inalterados. a memorização também é feita com o uso de "WeakMaps"). Aqui estão alguns exemplos:

Com objectos:

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é mesmo usar native call e apply para ligar alguns contexto:

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
 2
Author: Do Async, 2017-10-12 22:19:07

Você pode usar A Promessa nativa na ES6, por exemplo lidando 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 razão para falhar, por isso nunca é chamada.
 1
Author: Nicolas Zozol, 2017-01-27 13:00:54

A função callback style sempre assim (quase toda a função no nó.js is this style):

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

Este estilo tem a mesma característica:

  1. A função de callback é passada pelo último argumento.

  2. A função callback sempre aceita o objeto de erro como primeiro argumento.

Então, você poderia escrever uma função para converter uma função com este estilo:
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 um exemplo mais conciso, acima do utilizado ramda.js. Ramda.js é uma excelente biblioteca para programação funcional. Em código acima, nós usamos é aplicar (como javascript function.prototype.apply) e adicionar (como javascript function.prototype.push ). Então, poderíamos converter a função de estilo a 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 é própria pela bibliotecaberserk , é uma bifurcação funcional da biblioteca de programação por ramda.js (criar por mim).

Espero que esta resposta seja útil para ti.
 0
Author: JITuan LIn, 2017-07-30 13:46:20

es6-promisify converte funções baseadas em callback para funções baseadas em promessas.

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

const promisedFn = promisify(callbackedFn, args);

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

 0
Author: Pujan Srivastava, 2017-10-17 23:56:27

A minha versão promisificada de uma função callback é a função 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))

A função P exige que a assinatura de callback seja callback(error,result).

 0
Author: loretoparisi, 2017-11-29 23:34:06
Podes fazer uma coisa destas.
// @flow

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

export default toPromise

Depois usa-o

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

  console.log(friends)
}
 0
Author: onmyway133, 2018-10-09 13:35:18

Você pode usar callback2Promise pacote npm para converter funções de estilo do nó para promessas.`

var c2p = require('callback2promise');

// ordinary function with any number of parameters and a callback at the end 
var nodeStyleFunc = function(param1, param2, callback){
  setTimeout(
    function(){ 
    callback(null, 'done') 
  }, 200);
}

// convert the function to a promise 
var promise = c2p(nodeStyleFunc)(param1, param2);

promise
  .then(result => console.log(result))
  .catch(err => console.log(err));
 -1
Author: kutlu, 2016-09-28 13:45:50