Come si converte un'API di callback esistente in promesse?

Mar 20 2014

Voglio lavorare con le promesse ma ho un'API di callback in un formato come:

1. Caricamento DOM o altro evento occasionale:

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

};

2. Richiamata normale:

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

3. Richiamata stile nodo ("nodeback"):

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

4. Un'intera libreria con callback in stile nodo:

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

Come lavoro con l'API nelle promesse, come la "prometto"?

Risposte

769 BenjaminGruenbaum Mar 20 2014 at 05:47

Le promesse hanno uno stato, iniziano come in sospeso e possono essere stabilite:

  • soddisfatto significa che il calcolo è stato completato con successo.
  • rifiutato significa che il calcolo non è riuscito.

Promettere che le funzioni di ritorno non dovrebbero mai generare , dovrebbero invece restituire i rifiuti. Il lancio da una funzione di restituzione della promessa ti costringerà a utilizzare sia a } catch { che a .catch. Le persone che utilizzano API promesse non si aspettano che le promesse vengano lanciate. Se non sei sicuro di come funzionano le API asincrone in JS, leggi prima questa risposta .

1. Caricamento DOM o altro evento occasionale:

Quindi, creare promesse generalmente significa specificare quando si stabiliscono, ovvero quando passano alla fase soddisfatta o rifiutata per indicare che i dati sono disponibili (e possono essere consultati con .then).

Con implementazioni di promesse moderne che supportano il Promisecostruttore come promesse ES6 native:

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

Dovresti quindi utilizzare la promessa risultante in questo modo:

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

Con le librerie che supportano il differito (usiamo $ q per questo esempio qui, ma useremo anche jQuery in seguito):

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

O con un'API simile a jQuery, agganciare un evento che si verifica una volta:

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

2. Richiamata normale:

Queste API sono piuttosto comuni poiché beh ... i callback sono comuni in JS. Diamo un'occhiata al caso comune di avere onSuccesse onFail:

function getUserData(userId, onLoad, onFail) { …

Con implementazioni di promesse moderne che supportano il Promisecostruttore come promesse ES6 native:

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

Con le librerie che supportano il differito (usiamo jQuery per questo esempio qui, ma abbiamo anche usato $ q sopra):

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

jQuery offre anche un $.Deferred(fn)form, che ha il vantaggio di permetterci di scrivere un'espressione che emula molto da vicino il new Promise(fn)form, come segue:

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

Nota: qui sfruttiamo il fatto che i metodi resolvee i differiti di jQuery rejectsono "staccabili"; cioè. sono legati all'istanza di jQuery.Deferred (). Non tutte le librerie offrono questa funzionalità.

3. Richiamata stile nodo ("nodeback"):

I callback in stile nodo (nodeback) hanno un formato particolare in cui il callback è sempre l'ultimo argomento e il suo primo parametro è un errore. Promettiamo prima uno manualmente:

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

Per:

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

Con i differiti puoi fare quanto segue (usiamo Q per questo esempio, sebbene Q ora supporti la nuova sintassi che dovresti preferire ):

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

In generale, non dovresti promettere troppo le cose manualmente, la maggior parte delle librerie di promesse progettate pensando a Node e alle promesse native in Node 8+ hanno un metodo integrato per promettere nodeback. Per esempio

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

4. Un'intera libreria con callback in stile nodo:

Non esiste una regola d'oro qui, le prometti una per una. Tuttavia, alcune implementazioni di promesse ti consentono di farlo in blocco, ad esempio in Bluebird, convertire un'API nodeback in un'API promessa è semplice come:

Promise.promisifyAll(API);

O con promesse native in 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}), {});

Appunti:

  • Ovviamente, quando sei in un .thengestore non hai bisogno di promettere cose. La restituzione di una promessa da un .thengestore risolverà o rifiuterà con il valore di quella promessa. Anche il lancio da parte di un .thenconduttore è una buona pratica e rifiuterà la promessa: questa è la famosa promessa di lancio sicuro.
  • In un onloadcaso reale , dovresti usare addEventListenerinvece di onX.
58 efkan Jan 02 2017 at 20:19

Oggi, posso utilizzare Promisein Node.jsun metodo Javascript pianura.

Un esempio semplice e basilare per Promise(con il metodo KISS ):

Codice API asincrono JavaScript semplice :

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

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

    successCallback( number / divider )

}

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

     })

}

(Consiglio di visitare questa bellissima fonte )

Inoltre Promisepuò essere utilizzato congiuntamente async\awaita ES7per rendere il flusso del programma attesa per un fullfiledrisultato simile al seguente:

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

Un altro utilizzo con lo stesso codice utilizzando il .then()metodo

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

Promisepuò essere utilizzato anche su qualsiasi piattaforma basata su Node.js come react-native.

Bonus : un metodo ibrido
(si presume che il metodo di callback abbia due parametri come errore e risultato)

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 )

     })

}

Il metodo sopra può rispondere al risultato per la richiamata vecchio stile e gli usi Promise.

Spero che sia di aiuto.

35 SivaKannan Aug 11 2017 at 18:31

Prima di convertire una funzione come promessa In 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)
})

Dopo averlo convertito

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

Nel caso in cui sia necessario gestire più richieste

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

Non credo che il window.onloadsuggerimento di @Benjamin funzionerà sempre, poiché non rileva se viene chiamato dopo il caricamento. Sono stato morso da così tante volte. Ecco una versione che dovrebbe sempre funzionare:

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

Node.js 8.0.0 include una nuova util.promisify()API che consente alle API standard in stile callback Node.js di essere racchiuse in una funzione che restituisce una promessa. Di util.promisify()seguito è mostrato un esempio di utilizzo di .

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

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

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

Vedere Supporto migliorato per le promesse

14 Bruno May 16 2017 at 12:35

Nella release candidate per Node.js 8.0.0, c'è una nuova utility, util.promisify(ho scritto su util.promisify ), che incapsula la capacità di promettere qualsiasi funzione.

Non è molto diverso dagli approcci suggeriti nelle altre risposte, ma ha il vantaggio di essere un metodo centrale e non richiede dipendenze aggiuntive.

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

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

Quindi hai un readFilemetodo che restituisce un nativo Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
7 Apoorv Jun 20 2016 at 20:38

Puoi utilizzare le promesse native di JavaScript con Node JS.

Collegamento al codice 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

Con il semplice vecchio javaScript vanilla, ecco una soluzione per promettere un callback 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

La libreria Q di kriskowal include funzioni callback-to-promise. Un metodo come questo:

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

può essere convertito con Q.ninvoke

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
4 user1852503 Aug 04 2016 at 07:45

Quando si dispone di alcune funzioni che accettano una richiamata e si desidera invece che restituiscano una promessa, è possibile utilizzare questa funzione per eseguire la conversione.

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

Sotto il nodo v7.6 + che ha promesse e asincrone integrate:

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

Come usare:

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

In Node.js 8 puoi promettere metodi oggetto al volo usando questo modulo npm:

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

Utilizza util.promisify e Proxies in modo che i tuoi oggetti rimangano invariati. La memorizzazione viene eseguita anche con l'uso di WeakMaps). Ecco alcuni esempi:

Con oggetti:

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

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

Con funzioni:

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

Puoi anche usare nativo calle applyper legare un po 'di contesto:

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
2 NicolasZozol Jan 22 2017 at 20:22

Puoi utilizzare Promise nativo in ES6, ad esempio per quanto riguarda 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
    });

}

In questo esempio, la Promessa non ha motivo di fallire, quindi reject()non viene mai chiamata.

2 jituanlin Jul 30 2017 at 20:39

La funzione di stile di callback è sempre così (quasi tutte le funzioni in node.js sono questo stile):

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

Questo stile ha la stessa caratteristica:

  1. la funzione di callback viene passata dall'ultimo argomento.

  2. la funzione di callback accetta sempre l'oggetto errore come primo argomento.

Quindi, potresti scrivere una funzione per convertire una funzione con questo stile come questo:

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

Per più conciso, l'esempio sopra utilizzato ramda.js. Ramda.js è un'eccellente libreria per la programmazione funzionale. Nel codice sopra, abbiamo usato applica (come javascript function.prototype.apply) e append (come javascript function.prototype.push). Quindi, potremmo convertire la funzione di stile di callback in una funzione di stile promessa ora:

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

La funzione toPromise e checkErr è di proprietà della libreria berserk , è un fork della libreria di programmazione funzionale di ramda.js (creato da me).

Spero che questa risposta ti sia utile.

2 onmyway133 Oct 09 2018 at 20:35

Puoi fare qualcosa di simile

// @flow

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

export default toPromise

Quindi usalo

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

  console.log(friends)
}
1 Pujan Oct 18 2017 at 06:56

es6-promisify converte le funzioni basate su callback in funzioni basate su Promise.

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

const promisedFn = promisify(callbackedFn, args);

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

1 loretoparisi Nov 30 2017 at 06:34

La mia versione promessa di una callbackfunzione è la Pfunzione:

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

La Pfunzione richiede che la firma di callback sia callback(error,result).

1 Mzndako Jun 21 2019 at 17:38

Di seguito è riportata l'implementazione di come una funzione (callback API) può essere convertita in una 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

Dal futuro 😄

Una semplice funzione generica che utilizzo normalmente.

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

Come usarlo

   promisify(fn, arg1, arg2)

Probabilmente non stai cercando questa risposta, ma questo aiuterà a capire il funzionamento interno delle utilità disponibili