Como faço para converter uma API de retorno de chamada existente em promessas?

Mar 20 2014

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

1. Carregamento de 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 de estilo de nó ("nodeback"):

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

4. Uma biblioteca inteira com callbacks de estilo de nó:

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

Como faço para trabalhar com a API em promessas, como faço para "prometê-la"?

Respostas

769 BenjaminGruenbaum Mar 20 2014 at 05:47

As promessas têm estado, começam como pendentes e podem ser estabelecidas para:

  • cumprido, o que significa que o cálculo foi concluído com êxito.
  • rejeitado, o que significa que o cálculo falhou.

As funções de retorno de promessa nunca devem ser lançadas , mas sim rejeições. Jogar de uma função de retorno de promessa forçará você a usar tanto a } catch { quanto a .catch. Pessoas que usam APIs prometidas não esperam promessas. Se você não tem certeza de como as APIs assíncronas funcionam em JS - consulte esta resposta primeiro.

1. Carregamento de DOM ou outro evento único:

Portanto, criar promessas geralmente significa especificar quando elas são estabelecidas - ou seja, quando 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 promessas ES6 nativas:

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

Você então usaria a promessa resultante da seguinte forma:

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

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

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

Ou com um jQuery como API, enganchando em um evento acontecendo 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 promessas ES6 nativas:

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 a new Promise(fn)forma, da seguinte maneira:

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

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

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

Callbacks de 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 prometê-lo 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 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 prometer muito as coisas manualmente, a maioria das bibliotecas de promessa que foram projetadas com o Node em mente, bem como as promessas nativas no Node 8+, têm um método embutido 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 callbacks de estilo de nó:

Não há regra de ouro aqui, você os promete um por um. No entanto, algumas implementações de promessa permitem que você faça isso em massa, por exemplo no Bluebird, a conversão de uma API nodeback em uma API de promessa é 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:

  • É claro que, quando você está em um .thenmanipulador, não precisa prometer coisas. Retornar uma promessa de um .thenmanipulador resolverá ou rejeitará o valor dessa promessa. Arremessar de um .thenmanipulador também é uma boa prática e rejeitará a promessa - essa é a famosa segurança de arremesso de promessa.
  • Em um onloadcaso real , você deve usar em addEventListenervez de onX.
58 efkan Jan 02 2017 at 20:19

Hoje, posso usar o Promisein Node.jscomo um método simples de Javascript.

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

Código de API simples de JavaScript assíncrono:

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

Também Promisepode ser usado junto async\awaitcom o ES7para fazer o fluxo do programa esperar por 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 para callback à moda antiga e usos de promessa.

Espero que isto ajude.

35 SivaKannan Aug 11 2017 at 18:31

Antes de converter uma função como promessa em 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 atender a vários pedidos

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 Leo Jan 14 2015 at 11:15

Não creio que a window.onloadsugestão do @Benjamin funcione o tempo todo, pois não detecta se é chamado após o carregamento. Já fui mordido por 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);
15 GianMarco May 31 2017 at 13:46

O Node.js 8.0.0 inclui uma nova util.promisify()API que permite que APIs de estilo de retorno de chamada Node.js padrão 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) => { /** ... **/ });

Veja suporte aprimorado para promessas

14 Bruno May 16 2017 at 12:35

No candidato a lançamento do Node.js 8.0.0, há um novo utilitário util.promisify(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 requer 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(...);
7 Apoorv Jun 20 2016 at 20:38

Você pode usar promessas nativas de JavaScript com Node JS.

Link do 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
7 daviddavis Nov 28 2016 at 10:07

Com o velho e simples javaScript vanilla, aqui está uma solução para prometir um callback 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);
});
6 JasonLoveman Apr 08 2015 at 01:30

A biblioteca Q de kriskowal inclui funções de retorno 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) {
});
4 user1852503 Aug 04 2016 at 07:45

Quando você tem algumas funções que recebem um retorno de chamada e deseja que elas retornem uma promessa, 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;
    }
}
4 PaulSpaulding Apr 12 2017 at 23:48

No nó v7.6 + que possui promessas integradas 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;
}
3 DoAsync Oct 13 2017 at 05:19

No Node.js 8, você pode promisificar 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 => { /*...*/ });
2 NicolasZozol Jan 22 2017 at 20:22

Você pode usar Promise nativa no ES6, por exemplo, 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 razão para falhar, então reject()nunca é chamada.

2 jituanlin Jul 30 2017 at 20:39

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

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

Este estilo possui 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.

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

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 mais conciso, o exemplo acima usou ramda.js. Ramda.js é uma excelente biblioteca para programação funcional. No código acima, usamos aplicar (como javascript function.prototype.apply) e anexar (como javascript function.prototype.push). Então, podemos converter a função de estilo de retorno de chamada em função de estilo de promessa 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 é de propriedade da biblioteca berserk , é uma bifurcação de biblioteca de programação funcional por ramda.js (criada por mim).

Espero que esta resposta seja útil para você.

2 onmyway133 Oct 09 2018 at 20:35

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)
}
1 Pujan Oct 18 2017 at 06:56

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

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

const promisedFn = promisify(callbackedFn, args);

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

1 loretoparisi Nov 30 2017 at 06:34

Minha versão promissora 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).

1 Mzndako Jun 21 2019 at 17:38

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

1 JosiahNyarega Sep 25 2020 at 01:28

Do futuro 😄

Uma função genérica simples que normalmente uso.

const promisify = (fn, ...args) => {
  return new Promise((resolve, reject) => {
    fn(...args, (err, data) => {
      if (err) {
        return reject(err);
      }
      resolve(data);
    });
  });
};

Como usá-lo

   promisify(fn, arg1, arg2)

Você provavelmente não está olhando para esta resposta, mas isso ajudará a entender o funcionamento interno dos utilitários disponíveis