Come posso convertire un'API di callback esistente in promesse?


721

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

1. Caricamento DOM o altro evento singolo:

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

};

2. Richiamata semplice:

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

3. Callback stile nodo ("nodeback"):

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

4. Un'intera libreria con richiamate 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 posso "promuoverlo"?


Ho pubblicato la mia risposta, ma le risposte si espandono su come farlo per una libreria specifica o in più circostanze e anche le modifiche sono molto gradite.
Benjamin Gruenbaum,

@Bergi Questa è un'idea interessante, ho cercato di dare una risposta generale che utilizza i due approcci comuni (costruttore promessa e oggetto differito). Ho provato a dare le due alternative nelle risposte. Sono d'accordo sul fatto che RTFMing risolva questo problema, ma ci imbattiamo spesso in questo problema sia qui che nel bug tracker, quindi ho pensato che fosse presente una "domanda canonica" - Penso che RTFMing risolva circa il 50% dei problemi nel tag JS: D Se hai una visione interessante per contribuire a una risposta o modificarla sarebbe molto apprezzata.
Benjamin Gruenbaum,

La creazione di new Promiseun'aggiunta comporta un sovraccarico significativo? Voglio racchiudere tutte le mie funzioni sincrone di Noje.js in una promessa in modo da rimuovere tutto il codice sincrono dalla mia app Node, ma è questa la migliore pratica? In altre parole, una funzione che accetta un argomento statico (ad esempio una stringa) e restituisce un risultato calcolato, dovrei racchiuderlo in una promessa? ... Ho letto da qualche parte che non dovresti avere alcun codice sincrono in Nodejs.
Ronnie Royston,

1
@RonRoyston no, non è una buona idea avvolgere le chiamate sincrone con le promesse - solo chiamate asincrone che possono eseguire I / O
Benjamin Gruenbaum,

Risposte:


744

Le promesse hanno uno stato, iniziano come in sospeso e possono accontentarsi di:

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

Le funzioni di ritorno promettenti non dovrebbero mai essere lanciate , dovrebbero invece restituire rifiuti. Lanciare da una funzione di ritorno promessa ti costringerà a usare sia a } catch { che a .catch. Le persone che utilizzano API promisificate non si aspettano che promettano di lanciare. Se non sei sicuro di come funzionano le API asincrone in JS, consulta prima questa risposta .

1. Caricamento DOM o altro evento singolo:

Quindi, creare promesse in genere significa specificare quando si stabiliscono - ciò significa quando si spostano nella fase adempiuta o respinta per indicare che i dati sono disponibili (e sono accessibili con .then).

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

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

Utilizzeresti quindi 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 jQuery come, collegandosi a un evento che si verifica una volta:

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

2. Richiamata semplice:

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

function getUserData(userId, onLoad, onFail) { 

Con le moderne implementazioni delle promesse che supportano il Promisecostruttore come le 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)modulo, che ha il vantaggio di permetterci di scrivere un'espressione che emula molto da vicino il new Promise(fn)modulo, 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 rejectmetodi differiti di un jQuery sono "staccabili"; vale a dire. sono associati all'istanza di un jQuery.Deferred (). Non tutte le librerie offrono questa funzione.

3. Callback stile nodo ("nodeback"):

I callback (nodiback) in stile nodo hanno un formato particolare in cui i callback sono 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 rinvii 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 le cose manualmente troppo, la maggior parte delle librerie di promesse progettate pensando a Nodo, così come le promesse native in Nodo 8+ hanno un metodo incorporato per la promozione di nodi di nodo. 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 richiamate in stile nodo:

Non esiste una regola d'oro qui, li prometti uno per uno. Tuttavia, alcune implementazioni 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:

  • Certo, quando sei in un .thengestore non devi promettere cose. Restituzione di una promessa da a.then gestore risolverà o rifiuterà con il valore di quella promessa. Lanciare da un .thengestore è anche una buona pratica e rifiuterà la promessa - questa è la famosa promessa di sicurezza.
  • In un onloadcaso reale , dovresti usare addEventListenerpiuttosto che onX.

Benjamin, ho accettato il tuo invito a modificare e ho aggiunto un ulteriore esempio di jQuery al caso 2. Dovrà essere sottoposto a revisione tra pari prima che appaia. Spero ti piaccia.
Roamer-1888,

@ Roamer-1888 è stato rifiutato poiché non l'ho visto e l'ho accettato in tempo. Per quello che vale, non penso che l'aggiunta sia troppo rilevante sebbene utile.
Benjamin Gruenbaum,

2
Benjamin, anche resolve()e reject()sono scritti per essere riutilizzabile, oso che la mia modifica suggerita è rilevante perché offre un esempio jQuery della forma $.Deferred(fn), che è altrimenti carente. Se viene incluso solo un esempio di jQuery, allora suggerisco che dovrebbe essere di questa forma piuttosto che var d = $.Deferred();ecc. Poiché le persone dovrebbero essere incoraggiate a usare la $.Deferred(fn)forma spesso trascurata , inoltre, in una risposta come questa, mette jQuery più alla pari con librerie che usano il Revealing Constructor Pattern .
Roamer-1888,

Eh, per essere onesti al 100% non sapevo che jQuery te lo permettesse $.Deferred(fn), se lo modificassi invece dell'esempio esistente nei prossimi 15 minuti, sono sicuro di poter provare ad approvarlo in tempo :)
Benjamin Gruenbaum

7
Questa è un'ottima risposta Potresti aggiornarlo menzionando anche util.promisifyche Node.js aggiungerà al suo core a partire da RC 8.0.0. Il suo funzionamento non è molto diverso da Bluebird Promise.promisify, ma ha il vantaggio di non richiedere dipendenze aggiuntive, nel caso in cui desideri solo Promessa nativa. Ho scritto un post sul blog su util.promisify per chiunque voglia saperne di più sull'argomento.
Bruno,

55

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

Un esempio semplice e basilare di Promise (con KISS ):

pianuraCodice API asincrono Javascript :

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

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

    successCallback( number / divider )

}

Promise Codice API asincrono Javascript:

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 insieme async\awaitaES7 per 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 .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ò anche essere utilizzato 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 ai risultati per callback di vecchia moda e usi Promise.

Spero che sia di aiuto.


3
Questi non sembrano mostrare come convertirsi in promesse.
Dmitri Zaitsev,

33

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 richieste multiple

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

Non credo che il window.onloadsuggerimento di @Benjamin funzionerà continuamente, in quanto 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);

1
il ramo "già completo" non dovrebbe usare setTimeout(resolve, 0)(o setImmediate, se disponibile) per assicurarsi che venga chiamato in modo asincrono?
Alnitak,

5
@Alnitak La chiamata in modo resolvesincrono va bene. I thengestori di Promise sono garantiti dal framework per essere chiamati in modo asincrono , indipendentemente dal fatto che resolvesia chiamato in modo sincrono.
Jeff Bowman,

15

Node.js 8.0.0 include una nuova util.promisify()API che consente di racchiudere le API standard di callback in stile Node.js 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) => { /** ... **/ });

Vedi Supporto migliorato per le promesse


2
Ci sono già due risposte che descrivono questo, perché postarne una terza?
Benjamin Gruenbaum,

1
Solo perché la versione del nodo è stata rilasciata e ho segnalato una descrizione e un collegamento "ufficiali".
Gian Marco Gherardi,

14

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 promuovere qualunque funzione.

Non è molto diverso dagli approcci suggeriti nelle altre risposte, ma ha il vantaggio di essere un metodo di base e di non richiedere 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(...);

1
Ehi, io (OP) ho effettivamente suggerito util.promisifydue volte (nel 2014 quando questa domanda è stata scritta, e pochi mesi fa - che ho spinto come membro principale di Node ed è la versione corrente che abbiamo in Node). Dal momento che non è ancora disponibile pubblicamente, non l'ho ancora aggiunto a questa risposta. Apprezzeremmo molto il feedback sull'utilizzo e scopriremo quali sono alcune insidie ​​per avere documenti migliori per l'uscita :)
Benjamin Gruenbaum,

1
Inoltre, potresti voler discutere della bandiera personalizzata per la promozione util.promisifynel tuo post sul blog :)
Benjamin Gruenbaum,

@BenjaminGruenbaum Intendi il fatto che usando il util.promisify.customsimbolo è possibile sovrascrivere il risultato di util.promisify? Ad essere sincero, questa è stata una mancanza intenzionale, perché non sono ancora riuscito a trovare un caso d'uso utile. Forse puoi darmi degli input?
Bruno,

1
Certo, prendi in considerazione API come fs.existso API che non seguono la convenzione Node: un uccellino azzurro Promise.promisify avrebbe sbagliato, ma le avrebbe corrette util.promisify.
Benjamin Gruenbaum,

7

È possibile 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

Con il semplice vecchio javaScript alla vaniglia, ecco una soluzione per promuovere 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

La libreria Q di kriskowal include funzioni di call-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) {
});

1
La risposta canonica menziona già Q.denodeify. Dobbiamo enfatizzare gli aiutanti delle biblioteche?
Bergi,

3
l'ho trovato utile come google sulla promozione in Q conduce qui
Ed Sykes,

4

Quando si dispone di alcune funzioni che accettano una richiamata e si desidera 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

Sotto il nodo v7.6 + che ha incorporato promesse e asincrono:

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

In Node.js 8 puoi promisificare al volo metodi di oggetti 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 associare un certo contesto:

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

2

È possibile utilizzare Promise nativo in ES6, ad esempio per gestire 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

La funzione di callback style è sempre così (quasi tutte le funzioni in node.js sono di 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 è passata dall'ultimo argomento.

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

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

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, sopra l'esempio usato ramda.js. Ramda.js è una libreria eccellente per la programmazione funzionale. Nel codice sopra, abbiamo usato si applica (come javascript function.prototype.apply) e append (come javascript function.prototype.push). Quindi, ora potremmo convertire la funzione di stile callback in funzione di stile promessa:

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 di libreria di programmazione funzionale di ramda.js (creato da me).

Spero che questa risposta ti sia utile.


2

Puoi fare qualcosa del genere

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

2
Ehi, non sono sicuro di ciò che si aggiunge alle risposte esistenti (forse chiarire?). Inoltre, non è necessario il tentativo / cattura all'interno del costruttore della promessa (lo fa automaticamente per te). Non è anche chiaro per quali funzioni funzioni (che chiama il callback con un singolo argomento sul successo? Come vengono gestiti gli errori?)
Benjamin Gruenbaum,


1

La mia versione promisify 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 sia la firma di richiamata callback(error,result).


1
Che vantaggio ha questo rispetto alla promozione nativa o alle risposte sopra?
Benjamin Gruenbaum,

Cosa intendi per promisificare nativo?
Loretoparisi,


ah sì certo :). Solo ed esempio per mostrare l'idea di base. In effetti puoi vedere come anche quello nativo richiede che la firma della funzione debba essere definita (err, value) => ...o che tu debba definirne una personalizzata (vedi Funzioni promesse personalizzate). Grazie buon 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))); };farebbe la stessa cosa della tua ed è molto più semplice.
Patrick Roberts,

1

Di seguito è riportata l'implementazione di come una funzione (API di callback) 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');

-2

Sono passati circa 5 anni, ma volevo pubblicare qui la mia versione di promesify che prende le funzioni dall'API di callback e le trasforma in promesse

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

Dai un'occhiata a questa versione molto semplice qui: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a


1
Non è una promessa, non si incatena, non affronta gli errori generati nel callback o accetta un secondo parametro in ...
Benjamin Gruenbaum,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.