Comment convertir une API de rappel existante en promesses?


721

Je veux travailler avec des promesses mais j'ai une API de rappel dans un format comme:

1. Charge DOM ou autre événement ponctuel:

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

};

2. Rappel simple:

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

3. Rappel de style de nœud ("nodeback"):

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

4. Toute une bibliothèque avec des rappels de style nœud:

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

Comment utiliser l'API dans les promesses, comment la "promettre"?


J'ai posté ma propre réponse, mais les réponses développant comment le faire pour une bibliothèque spécifique ou dans plus de circonstances et de modifications sont également les bienvenues.
Benjamin Gruenbaum

@Bergi C'est une idée intéressante, j'ai essayé de faire une réponse générale qui utilise les deux approches communes (constructeur Promise et objet différé). J'ai essayé de donner les deux alternatives dans les réponses. Je suis d'accord que RTFMing résout ce problème, mais nous rencontrons souvent ce problème ici et dans le suivi des bogues, donc j'ai pensé qu'une `` question canonique '' était en place - je pense que RTFMing résout environ 50% des problèmes du tag JS: D Si vous avez une idée intéressante à apporter dans une réponse ou à la modifier, ce serait très apprécié.
Benjamin Gruenbaum

La création d'un new Promiseajout entraîne- t-elle des frais généraux importants? Je souhaite encapsuler toutes mes fonctions Noje.js synchrones dans une promesse afin de supprimer tout le code synchrone de mon application Node, mais est-ce la meilleure pratique? En d'autres termes, une fonction qui accepte un argument statique (par exemple une chaîne) et renvoie un résultat calculé, dois-je envelopper cela dans une promesse? ... J'ai lu quelque part que vous ne devriez pas avoir de code synchrone dans Nodejs.
Ronnie Royston

1
@RonRoyston non, ce n'est pas une bonne idée d'envelopper les appels synchrones avec des promesses - uniquement des appels asynchrones qui peuvent effectuer des E / S
Benjamin Gruenbaum

Réponses:


744

Les promesses ont un statut, elles commencent comme en suspens et peuvent se régler pour:

  • remplie, ce qui signifie que le calcul s'est terminé avec succès.
  • rejeté, ce qui signifie que le calcul a échoué.

Les fonctions de retour de promesse ne devraient jamais être lancées , elles devraient plutôt renvoyer les rejets. Le fait de quitter une fonction de retour de promesse vous obligera à utiliser à la fois a } catch { et a .catch. Les personnes utilisant des API promises ne s'attendent pas à ce que des promesses soient lancées. Si vous n'êtes pas sûr du fonctionnement des API asynchrones dans JS, veuillez d'abord lire cette réponse .

1. Charge DOM ou autre événement ponctuel:

Donc, créer des promesses signifie généralement spécifier quand ils s'installent - cela signifie quand ils passent à la phase remplie ou rejetée pour indiquer que les données sont disponibles (et accessibles avec .then).

Avec des implémentations de promesses modernes qui prennent en charge le Promiseconstructeur comme les promesses natives d'ES6:

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

Vous utiliseriez alors la promesse résultante comme ceci:

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

Avec les bibliothèques qui prennent en charge différé (utilisons $ q pour cet exemple ici, mais nous utiliserons également jQuery plus tard):

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

Ou avec une API comme jQuery, accrocher un événement qui se produit une fois:

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

2. Rappel simple:

Ces API sont plutôt courantes car bon… les rappels sont courants dans JS. Regardons le cas commun d'avoir onSuccesset onFail:

function getUserData(userId, onLoad, onFail) { 

Avec des implémentations de promesses modernes qui prennent en charge le Promiseconstructeur comme les promesses natives d'ES6:

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

Avec les bibliothèques qui prennent en charge différé (utilisons jQuery pour cet exemple ici, mais nous avons également utilisé $ q ci-dessus):

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

jQuery propose également un $.Deferred(fn)formulaire, qui a l'avantage de nous permettre d'écrire une expression qui émule de très près le new Promise(fn)formulaire, comme suit:

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

Remarque: Ici, nous exploitons le fait qu'un jQuery différé resolveet les rejectméthodes sont "détachables"; c'est à dire. ils sont liés à l' instance d'un jQuery.Deferred (). Toutes les bibliothèques n'offrent pas cette fonctionnalité.

3. Rappel de style de nœud ("nodeback"):

Les rappels de style nœud (nœuds) ont un format particulier où les rappels sont toujours le dernier argument et son premier paramètre est une erreur. Promettons d'abord un manuellement:

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

À:

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

Avec les reports, vous pouvez effectuer les opérations suivantes (utilisons Q pour cet exemple, bien que Q supporte désormais la nouvelle syntaxe que vous devriez préférer ):

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

En général, vous ne devez pas trop promettre des choses manuellement, la plupart des bibliothèques de promesses conçues avec Node à l'esprit ainsi que les promesses natives dans Node 8+ ont une méthode intégrée pour promettre les dos de nœud. Par exemple

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

4. Toute une bibliothèque avec des rappels de style nœud:

Il n'y a pas de règle d'or ici, vous les promettez un par un. Cependant, certaines implémentations de promesse vous permettent de le faire en bloc, par exemple dans Bluebird, la conversion d'une API nodeback en API de promesse est aussi simple que:

Promise.promisifyAll(API);

Ou avec des promesses natives dans 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}), {});

Remarques:

  • Bien sûr, lorsque vous êtes dans un .thengestionnaire, vous n'avez pas besoin de promettre des choses. Le renvoi d'une promesse d'un .thengestionnaire résoudra ou rejettera la valeur de cette promesse. Lancer d'un .thengestionnaire est également une bonne pratique et rejettera la promesse - c'est la fameuse promesse de sécurité du lancer.
  • Dans un onloadcas réel , vous devez utiliser addEventListenerplutôt que onX.

Benjamin, j'ai accepté votre invitation à modifier et ajouté un autre exemple jQuery au cas 2. Il faudra un examen par les pairs avant qu'il n'apparaisse. J'espère que vous aimez.
Roamer-1888

@ Roamer-1888, il a été rejeté car je ne l'ai pas vu et accepté à temps. Pour ce que ça vaut, je ne pense pas que l'ajout soit trop pertinent bien qu'utile.
Benjamin Gruenbaum

2
Benjamin, si oui ou non resolve()et reject()sont écrites pour être réutilisables, je me risque que ma modifier proposée est pertinente car elle offre un exemple jQuery de la forme $.Deferred(fn), qui est par ailleurs défaut. Si un seul exemple jQuery est inclus, je suggère qu'il devrait être de cette forme plutôt que var d = $.Deferred();etc., car les gens devraient être encouragés à utiliser la $.Deferred(fn)forme souvent négligée , de plus, dans une réponse comme celle-ci, cela place jQuery plus sur un pied d'égalité avec bibliothèques qui utilisent le modèle de constructeur révélateur .
Roamer-1888 du

Hé, pour être 100% juste, je ne savais pas que jQuery vous laissait le faire $.Deferred(fn), si vous modifiez cela dans au lieu de l'exemple existant dans les 15 prochaines minutes, je suis sûr que je peux essayer de l'approuver à temps :)
Benjamin Gruenbaum

7
C'est une excellente réponse. Vous voudrez peut-être le mettre à jour en mentionnant également util.promisifyque Node.js va ajouter à son noyau à partir de RC 8.0.0. Son fonctionnement n'est pas très différent de Bluebird Promise.promisify, mais a l'avantage de ne pas nécessiter de dépendances supplémentaires, au cas où vous voudriez simplement Promise natif. J'ai écrit un article de blog sur util.promisify pour tous ceux qui veulent en savoir plus sur le sujet.
Bruno

55

Aujourd'hui, je peux utiliser Promisein Node.jscomme une simple méthode Javascript.

Un exemple simple et basique pour Promise(avec la méthode KISS ):

Code API Async Javascript simple :

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

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

    successCallback( number / divider )

}

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

     })

}

(Je recommande de visiter cette belle source )

PromisePeut également être utilisé avec together async\awaitin ES7pour que le flux de programme attende un fullfiledrésultat comme celui-ci:

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

Une autre utilisation avec le même code en utilisant la .then()méthode

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

Promisepeut également être utilisé sur n'importe quelle plate-forme basée sur Node.js comme react-native.

Bonus : une méthode hybride
(la méthode de rappel est supposée avoir deux paramètres comme erreur et résultat)

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 )

     })

}

La méthode ci-dessus peut répondre au résultat pour les utilisations de rappel à l'ancienne et Promise.

J'espère que cela t'aides.


3
Ceux-ci ne semblent pas montrer comment se convertir aux promesses.
Dmitri Zaitsev

33

Avant de convertir une fonction en promesse dans 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)
})

Après l'avoir converti

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

Dans le cas où vous devez gérer plusieurs demandes

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

Je ne pense pas que la window.onloadsuggestion de @Benjamin fonctionnera tout le temps, car elle ne détecte pas si elle est appelée après le chargement. J'en ai été mordu plusieurs fois. Voici une version qui devrait toujours fonctionner:

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);

1
la branche "déjà terminée" ne devrait-elle pas être utilisée setTimeout(resolve, 0)(ou setImmediate, si elle est disponible) pour s'assurer qu'elle est appelée de manière asynchrone?
Alnitak

5
@Alnitak L'appel resolvesynchrone est très bien. Les thengestionnaires de Promise sont garantis par le framework d'être appelés de manière asynchrone , qu'ils resolvesoient appelés de manière synchrone ou non.
Jeff Bowman

15

Node.js 8.0.0 inclut une nouvelle util.promisify()API qui permet aux API de style de rappel Node.js standard d'être encapsulées dans une fonction qui renvoie une promesse. Un exemple d'utilisation de util.promisify()est illustré ci-dessous.

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

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

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

Voir Prise en charge améliorée des promesses


2
Il y a déjà deux réponses décrivant cela, pourquoi en poster une troisième?
Benjamin Gruenbaum

1
Tout simplement parce que cette version du nœud est maintenant disponible, et j'ai signalé la description et le lien de la fonctionnalité "officielle".
Gian Marco Gherardi

14

Dans la version candidate pour Node.js 8.0.0, il y a un nouvel utilitaire, util.promisify(j'ai écrit sur util.promisify ), qui encapsule la capacité de promettre n'importe quelle fonction.

Elle n'est pas très différente des approches suggérées dans les autres réponses, mais a l'avantage d'être une méthode de base et de ne pas nécessiter de dépendances supplémentaires.

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

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

Ensuite, vous avez une readFileméthode qui renvoie un natif Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);

1
Hé, j'ai (OP) en fait suggéré util.promisifydeux fois (en 2014 lorsque cette question a été écrite, et il y a quelques mois - ce que j'ai préconisé en tant que membre principal de Node et c'est la version actuelle que nous avons dans Node). Comme il n'est pas encore accessible au public, je ne l'ai pas encore ajouté à cette réponse. Nous apprécierions profondément les commentaires d'utilisation et de savoir quels sont les pièges afin d'avoir de meilleurs documents pour la sortie :)
Benjamin Gruenbaum

1
De plus, vous voudrez peut-être discuter du drapeau personnalisé à promettre avec util.promisifydans votre article de blog :)
Benjamin Gruenbaum

@BenjaminGruenbaum Voulez-vous dire le fait qu'en utilisant le util.promisify.customsymbole, il est possible de remplacer le résultat de util.promisify? Pour être honnête, c'était une erreur intentionnelle, car je ne suis pas encore en mesure de trouver un cas d'utilisation utile. Vous pouvez peut-être me donner quelques informations?
Bruno

1
Bien sûr, considérez les API comme fs.existsou les API qui ne respectent pas la convention Node - un oiseau bleu Promise.promisify se tromperait, mais util.promisifyles ferait bien.
Benjamin Gruenbaum

7

Vous pouvez utiliser des promesses natives JavaScript avec Node JS.

Lien de code 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

Avec le vieux javaScript vanilla, voici une solution pour promettre un rappel 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 bibliothèque Q de kriskowal comprend des fonctions de rappel à la promesse. Une méthode comme celle-ci:

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

peut être converti avec Q.ninvoke

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});

1
La réponse canonique mentionne déjà Q.denodeify. Faut-il mettre l'accent sur les aides de bibliothèque?
Bergi

3
j'ai trouvé cela utile en tant que google pour promettre des pistes Q ici
Ed Sykes

4

Lorsque vous avez quelques fonctions qui prennent un rappel et que vous souhaitez qu'elles renvoient une promesse, vous pouvez utiliser cette fonction pour effectuer la conversion.

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

Sous le nœud v7.6 + qui a intégré des promesses et asynchrones:

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

Comment utiliser:

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

Dans Node.js 8, vous pouvez promettre des méthodes d'objet à la volée en utilisant ce module npm:

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

Il utilise util.promisify et Proxies pour que vos objets restent inchangés. La mémorisation se fait également avec l'utilisation de WeakMaps). Voici quelques exemples:

Avec des objets:

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

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

Avec des fonctions:

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

Vous pouvez même utiliser natif callet applypour lier un certain contexte:

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

2

Vous pouvez utiliser Promise native dans ES6, par exemple pour traiter 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
    });

}

Dans cet exemple, la Promesse n'a aucune raison d'échouer, elle reject()n'est donc jamais appelée.


2

La fonction de style de rappel est toujours comme ça (presque toutes les fonctions dans node.js sont ce style):

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

Ce style a la même caractéristique:

  1. la fonction de rappel est passée par le dernier argument.

  2. la fonction de rappel accepte toujours l'objet d'erreur comme premier argument.

Donc, vous pouvez écrire une fonction pour convertir une fonction avec ce style comme ceci:

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

Pour un exemple plus concis, ci-dessus utilisé ramda.js. Ramda.js est une excellente bibliothèque pour la programmation fonctionnelle. Dans le code ci-dessus, nous avons utilisé l' appliquer (comme javascript function.prototype.apply) et l'ajouter (comme javascript function.prototype.push). Donc, nous pourrions convertir la fonction de style de rappel en fonction de style de promesse maintenant:

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

Promouvoir et fonction checkErr appartient à la bibliothèque berserk , c'est une fourchette de bibliothèque de programmation fonctionnelle par ramda.js (créée par moi).

J'espère que cette réponse vous sera utile.


2

Vous pouvez faire quelque chose comme ça

// @flow

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

export default toPromise

Ensuite, utilisez-le

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

  console.log(friends)
}

2
Hé, je ne sais pas ce que cela ajoute aux réponses existantes (peut-être clarifier?). De plus, il n'y a pas besoin de try / catch dans le constructeur de promesses (il le fait automatiquement pour vous). On ne sait pas non plus pour quelles fonctions cela fonctionne (qui appellent le rappel avec un seul argument sur le succès? Comment les erreurs sont-elles gérées?)
Benjamin Gruenbaum


1

Ma version promis d'une callbackfonction est la Pfonction:

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 Pfonction nécessite que la signature de rappel soit callback(error,result).


1
Quel avantage cela a-t-il sur les promesses natives ou sur les réponses ci-dessus?
Benjamin Gruenbaum

Que voulez-vous dire par promesse native?
loretoparisi


ah oui bien sûr :). Juste et exemple pour montrer l'idée de base. En fait, vous pouvez voir comment même le natif exige que la signature de la fonction soit définie comme (err, value) => ...ou vous devez en définir une personnalisée (voir Fonctions promises personnalisées). Merci bon 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))); };ferait la même chose que la vôtre et c'est beaucoup plus simple.
Patrick Roberts

1

Vous trouverez ci-dessous l'implémentation de la façon dont une fonction (API de rappel) peut être convertie en promesse.

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

C'est comme 5 ans de retard, mais je voulais poster ici ma version promesify qui prend les fonctions de l'API de rappel et les transforme en promesses

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

Jetez un œil à cette version très simple ici: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a


1
Ce n'est pas une promesse, il ne chaîne pas, beaucoup d'erreurs jeté dans le rappel ou accepter un second paramètre , puis ...
Benjamin Gruenbaum
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.