Wie konvertiere ich eine vorhandene Callback-API in Versprechen?


721

Ich möchte mit Versprechungen arbeiten, habe aber eine Rückruf-API in einem Format wie:

1. DOM-Last oder ein anderes einmaliges Ereignis:

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

};

2. Einfacher Rückruf:

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

3. Rückruf im Knotenstil ("Nodeback"):

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

4. Eine ganze Bibliothek mit Rückrufen im Knotenstil:

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

Wie arbeite ich mit der API in Versprechungen, wie "verspreche" ich sie?


Ich habe meine eigene Antwort gepostet, aber Antworten, die die Vorgehensweise für eine bestimmte Bibliothek oder unter bestimmten Umständen erweitern, sowie Änderungen sind sehr willkommen.
Benjamin Gruenbaum

@Bergi Das ist eine interessante Idee. Ich habe versucht, eine allgemeine Antwort zu geben, die die beiden gängigen Ansätze verwendet (Promise-Konstruktor und verzögertes Objekt). Ich habe versucht, die beiden Alternativen in Antworten zu geben. Ich bin damit einverstanden, dass RTFMing dieses Problem löst, aber wir stoßen sowohl hier als auch im Bug-Tracker häufig auf dieses Problem, sodass ich dachte, dass eine „kanonische Frage“ vorhanden ist. Ich denke, RTFMing löst etwa 50% der Probleme im JS-Tag: D If Sie haben einen interessanten Einblick, um zu einer Antwort beizutragen oder sie zu bearbeiten. Wir wären sehr dankbar.
Benjamin Gruenbaum

Fügt das Erstellen eines new Promisezusätzlichen Overheads hinzu? Ich möchte alle meine synchronen Noje.js-Funktionen in ein Versprechen einbinden, um den gesamten synchronen Code aus meiner Node-App zu entfernen. Ist dies jedoch die beste Vorgehensweise? Mit anderen Worten, eine Funktion, die ein statisches Argument (z. B. eine Zeichenfolge) akzeptiert und ein berechnetes Ergebnis zurückgibt, sollte ich das in ein Versprechen einschließen? ... Ich habe irgendwo gelesen, dass Sie in Nodejs keinen synchronen Code haben sollten.
Ronnie Royston

1
@ RonRoyston Nein, es ist keine gute Idee, synchrone Anrufe mit Versprechungen zu versehen - nur asynchrone Anrufe, die E / A ausführen können
Benjamin Gruenbaum

Antworten:


744

Versprechen haben Status, sie beginnen als ausstehend und können sich mit folgenden Punkten zufrieden geben:

  • erfüllt bedeutet, dass die Berechnung erfolgreich abgeschlossen wurde.
  • abgelehnt, was bedeutet, dass die Berechnung fehlgeschlagen ist.

Versprechende Rückgabefunktionen sollten niemals ausgelöst werden , sondern stattdessen Ablehnungen zurückgeben. Wenn Sie eine Funktion zum Zurückgeben von Versprechungen deaktivieren, werden Sie gezwungen, sowohl a } catch { als auch a zu verwenden .catch. Menschen, die vielversprechende APIs verwenden, erwarten keine Versprechen. Wenn Sie sich nicht sicher sind, wie asynchrone APIs in JS funktionieren, lesen Sie zuerst diese Antwort .

1. DOM-Last oder ein anderes einmaliges Ereignis:

Das Erstellen von Versprechungen bedeutet also im Allgemeinen, anzugeben, wann sie sich niederlassen - das heißt, wann sie in die erfüllte oder abgelehnte Phase wechseln, um anzuzeigen, dass die Daten verfügbar sind (und mit denen zugegriffen werden kann .then).

Mit modernen Versprechen-Implementierungen, die den PromiseKonstruktor wie native ES6-Versprechen unterstützen:

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

Sie würden dann das resultierende Versprechen wie folgt verwenden:

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

Mit Bibliotheken, die verzögert unterstützen (Verwenden wir hier $ q für dieses Beispiel, aber wir werden später auch jQuery verwenden):

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

Oder mit einer jQuery-ähnlichen API, die sich auf ein Ereignis einlässt, das einmal passiert:

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

2. Einfacher Rückruf:

Diese APIs sind ziemlich häufig, da Rückrufe in JS häufig vorkommen. Schauen wir uns den häufigen Fall von onSuccessund an onFail:

function getUserData(userId, onLoad, onFail) { 

Mit modernen Versprechen-Implementierungen, die den PromiseKonstruktor wie native ES6-Versprechen unterstützen:

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

Mit Bibliotheken, die verzögert unterstützen (Verwenden wir hier für dieses Beispiel jQuery, aber wir haben oben auch $ q verwendet):

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

jQuery bietet auch ein $.Deferred(fn)Formular an, das den Vorteil hat, dass wir einen Ausdruck schreiben können, der das new Promise(fn)Formular wie folgt sehr genau emuliert :

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

Hinweis: Hier nutzen wir die Tatsache, dass die zurückgestellten resolveund rejectMethoden einer jQuery "abnehmbar" sind. dh. Sie sind an die Instanz einer jQuery.Deferred () gebunden . Nicht alle Bibliotheken bieten diese Funktion an.

3. Rückruf im Knotenstil ("Nodeback"):

Rückrufe im Knotenstil (Nodebacks) haben ein bestimmtes Format, bei dem die Rückrufe immer das letzte Argument sind und der erste Parameter ein Fehler ist. Lassen Sie uns zunächst eine manuell versprechen:

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

Zu:

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

Mit Zurückgestellten können Sie Folgendes tun (verwenden wir Q für dieses Beispiel, obwohl Q jetzt die neue Syntax unterstützt, die Sie bevorzugen sollten ):

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

Im Allgemeinen sollten Sie Dinge nicht zu viel manuell versprechen. Die meisten Versprechungsbibliotheken, die unter Berücksichtigung von Node entwickelt wurden, sowie native Versprechungen in Node 8+ verfügen über eine integrierte Methode zum Versprechen von Nodebacks. Zum Beispiel

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

4. Eine ganze Bibliothek mit Rückrufen im Knotenstil:

Hier gibt es keine goldene Regel, Sie versprechen sie eins nach dem anderen. Bei einigen Versprechen-Implementierungen können Sie dies jedoch in großen Mengen tun, z. B. in Bluebird. Das Konvertieren einer Nodeback-API in eine Versprechen-API ist so einfach wie:

Promise.promisifyAll(API);

Oder mit einheimischen Versprechungen 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}), {});

Anmerkungen:

  • Wenn Sie sich in einem .thenHandler befinden, müssen Sie natürlich keine Dinge versprechen. Wenn Sie ein Versprechen von einem .thenHandler zurückgeben, wird es mit dem Wert dieses Versprechens aufgelöst oder abgelehnt. Das Werfen von einem .thenHandler ist ebenfalls eine gute Praxis und lehnt das Versprechen ab - dies ist das berühmte Versprechen, Sicherheit zu werfen.
  • In einem tatsächlichen onloadFall sollten Sie addEventListenereher als verwenden onX.

Benjamin, ich habe Ihre Einladung zur Bearbeitung angenommen und Fall 2 ein weiteres jQuery-Beispiel hinzugefügt. Bevor es angezeigt wird, muss es einer Peer-Review unterzogen werden. Hoffe du magst es.
Roamer-1888

@ Roamer-1888 es wurde abgelehnt, da ich es nicht rechtzeitig gesehen und akzeptiert habe. Für das, was es wert ist, denke ich nicht, dass der Zusatz zu relevant ist, obwohl er nützlich ist.
Benjamin Gruenbaum

2
Benjamin, ob oder nicht resolve()und reject()geschrieben werden , wiederverwendbar ist, wage ich , dass meine vorgeschlagen bearbeiten relevant ist , weil es ein jQuery Beispiel für die Form bietet $.Deferred(fn), die sonst fehlt. Wenn nur ein jQuery-Beispiel enthalten ist, dann schlage ich vor, dass es eher von dieser Form als von var d = $.Deferred();usw. sein sollte, da die Leute ermutigt werden sollten, die oft vernachlässigte $.Deferred(fn)Form zu verwenden, und in einer Antwort wie dieser jQuery mehr auf eine Stufe mit Bibliotheken, die das Revealing Constructor Pattern verwenden .
Roamer-1888

Um 100% fair zu sein, wusste ich nicht, dass jQuery es zulässt $.Deferred(fn). Wenn Sie das in den nächsten 15 Minuten anstelle des vorhandenen Beispiels bearbeiten, kann ich sicher versuchen, es rechtzeitig zu genehmigen :)
Benjamin Gruenbaum

7
Dies ist eine großartige Antwort. Sie können es aktualisieren, indem Sie auch erwähnen util.promisify, dass Node.js ab RC 8.0.0 zu seinem Kern hinzugefügt wird. Es funktioniert nicht viel anders als Bluebird Promise.promisify, hat aber den Vorteil, dass keine zusätzlichen Abhängigkeiten erforderlich sind, falls Sie nur native Promise möchten. Ich habe einen Blog-Beitrag über util.promisify für alle geschrieben, die mehr über das Thema lesen möchten.
Bruno

55

Heute kann ich verwenden Promisein Node.jsals einfache JavaScript - Methode.

Ein einfaches und grundlegendes Beispiel für Promise(mit KISS Weg):

Einfacher Javascript Async API-Code:

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

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

    successCallback( number / divider )

}

Promise Javascript Async API-Code:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}

(Ich empfehle diese schöne Quelle zu besuchen )

Auch Promisekann mit zusammen verwendet werden , async\awaitin , ES7um den Programmablauf Wartezeit für ein macht fullfiledErgebnis wie folgt aus :

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

Eine andere Verwendung mit demselben Code unter Verwendung der .then()Methode

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

Promisekann auch auf jeder Plattform verwendet werden, die auf Node.js wie basiert react-native.

Bonus : Ein Hybridverfahren (Die Callback - Methode wird angenommen , zwei Parameter als Fehler und das Ergebnis haben)

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 )

     })

}

Die obige Methode kann auf Ergebnisse für altmodische Rückruf- und Promise-Verwendungen reagieren.

Hoffe das hilft.


3
Diese scheinen nicht zu zeigen, wie man in Versprechen konvertiert.
Dmitri Zaitsev

33

Vor dem Konvertieren einer Funktion als Versprechen 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)
})

Nach dem Konvertieren

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

Falls Sie mehrere Anfragen bearbeiten müssen

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

Ich denke nicht, dass der window.onloadVorschlag von @Benjamin die ganze Zeit funktionieren wird, da er nicht erkennt, ob er nach dem Laden aufgerufen wird. Das hat mich schon oft gebissen. Hier ist eine Version, die immer funktionieren sollte:

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

1
Sollte der Zweig "bereits abgeschlossen" nicht setTimeout(resolve, 0)(oder setImmediate, falls verfügbar) verwendet werden, um sicherzustellen, dass er asynchron aufgerufen wird?
Alnitak

5
@Alnitak resolveSynchron anrufen ist in Ordnung. Die thenHandler des Promise werden durch das Framework garantiert, das asynchron aufgerufen wird , unabhängig davon, ob resolvees synchron aufgerufen wird.
Jeff Bowman

15

Node.js 8.0.0 enthält eine neue util.promisify()API, mit der Standard-APIs im Node.js-Rückrufstil in eine Funktion eingeschlossen werden können, die ein Versprechen zurückgibt. Ein Beispiel für die Verwendung von util.promisify()ist unten gezeigt.

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

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

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

Siehe Verbesserte Unterstützung für Versprechen


2
Es gibt bereits zwei Antworten, die dies beschreiben. Warum eine dritte posten?
Benjamin Gruenbaum

1
Nur weil diese Version des Knotens jetzt veröffentlicht ist und ich eine "offizielle" Funktionsbeschreibung und einen Link gemeldet habe.
Gian Marco Gherardi

14

In Release Candidate für Node.js 8.0.0 gibt es ein neues Dienstprogramm util.promisify(ich habe über util.promisify geschrieben ), das die Fähigkeit enthält, jede Funktion zu versprechen.

Es unterscheidet sich nicht wesentlich von den in den anderen Antworten vorgeschlagenen Ansätzen, hat jedoch den Vorteil, dass es eine Kernmethode ist und keine zusätzlichen Abhängigkeiten erfordert.

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

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

Dann haben Sie eine readFileMethode, die eine native zurückgibt Promise.

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

1
Hey, ich (OP) habe tatsächlich util.promisifyzweimal vorgeschlagen (im Jahr 2014, als diese Frage geschrieben wurde, und vor einigen Monaten - was ich als Kernmitglied von Node forciert habe und die aktuelle Version ist, die wir in Node haben). Da es noch nicht öffentlich verfügbar ist, habe ich es dieser Antwort noch nicht hinzugefügt. Wir würden uns jedoch sehr über Feedback zur Nutzung freuen und wissen, was einige Fallstricke sind, um bessere Dokumente für die Veröffentlichung zu haben :)
Benjamin Gruenbaum

1
Darüber hinaus möchten Sie vielleicht die benutzerdefinierte Flagge für das Versprechen util.promisifyin Ihrem Blog-Beitrag diskutieren :)
Benjamin Gruenbaum

@BenjaminGruenbaum Meinst du die Tatsache, dass es mit dem util.promisify.customSymbol möglich ist, das Ergebnis von util.promisify zu überschreiben? Um ehrlich zu sein, war dies ein absichtlicher Fehler, da ich noch keinen nützlichen Anwendungsfall finden kann. Vielleicht kannst du mir ein paar Inputs geben?
Bruno

1
Sicher, betrachten Sie APIs wie fs.existsoder APIs, die nicht der Node-Konvention folgen - ein Bluebird Promise.promisify würde sie falsch verstehen , aber util.promisifysie richtig machen.
Benjamin Gruenbaum

7

Sie können native JavaScript-Versprechen mit Node JS verwenden.

Mein Cloud 9-Code-Link: 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

Mit einfachem altem Vanille-JavaScript ist hier eine Lösung, um einen API-Rückruf zu versprechen.

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

Die Q-Bibliothek von kriskowal enthält Callback-to-Promise-Funktionen. Eine Methode wie diese:

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

kann mit Q.ninvoke konvertiert werden

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

1
Die kanonische Antwort erwähnt bereits Q.denodeify. Müssen wir Bibliothekshelfer hervorheben?
Bergi

3
Ich fand dies nützlich als Google über das Versprechen in Q führt hier
Ed Sykes

4

Wenn Sie einige Funktionen haben, die einen Rückruf annehmen, und Sie möchten, dass sie stattdessen ein Versprechen zurückgeben, können Sie diese Funktion verwenden, um die Konvertierung durchzuführen.

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

Unter dem Knoten v7.6 +, der Versprechen und Asynchronität eingebaut hat:

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

Wie benutzt man:

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 können Sie promisify Objektmethoden on the fly mit diesem npm Modul:

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

Es verwendet util.promisify und Proxies, damit Ihre Objekte unverändert bleiben. Das Auswendiglernen erfolgt auch mithilfe von WeakMaps. Hier sind einige Beispiele:

Mit Objekten:

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

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

Mit Funktionen:

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

Sie können sogar native verwenden callund applyeinen Kontext binden:

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

2

Sie können natives Versprechen in ES6 verwenden, um beispielsweise setTimeout zu behandeln:

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 diesem Beispiel hat das Versprechen keinen Grund zum Scheitern und wird daher reject()niemals genannt.


2

Die Callback-Style- Funktion sieht immer so aus (fast alle Funktionen in node.js sind dieser Style):

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

Dieser Stil hat die gleiche Funktion:

  1. Die Rückruffunktion wird vom letzten Argument übergeben.

  2. Die Rückruffunktion akzeptiert immer das Fehlerobjekt als erstes Argument.

Sie könnten also eine Funktion zum Konvertieren einer Funktion mit diesem Stil wie folgt schreiben:

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

Für eine präzisere Darstellung wurde im obigen Beispiel ramda.js verwendet. Ramda.js ist eine hervorragende Bibliothek für die funktionale Programmierung. Im obigen Code haben wir Apply (wie Javascript function.prototype.apply) und Append (wie Javascript function.prototype.push) verwendet. Wir könnten also die Funktion a callback style jetzt in die Funktion function versprechen konvertieren:

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

Die Funktion toPromise and checkErr gehört der Berserker- Bibliothek. Es handelt sich um eine funktionale Programmierbibliothek von ramda.js (von mir erstellt).

Hoffe, diese Antwort ist nützlich für Sie.


2

Sie können so etwas tun

// @flow

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

export default toPromise

Dann benutze es

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

  console.log(friends)
}

2
Hey, ich bin mir nicht sicher, was dies zu bestehenden Antworten beiträgt (vielleicht klarstellen?). Außerdem ist das Versuchen / Fangen im Versprechen-Konstruktor nicht erforderlich (dies erledigt dies automatisch für Sie). Es ist auch unklar, für welche Funktionen dies funktioniert (die den Rückruf mit einem einzigen Argument für den Erfolg aufrufen? Wie werden Fehler behandelt?)
Benjamin Gruenbaum


1

Meine vielversprechende Version einer callbackFunktion ist die PFunktion:

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

Die PFunktion erfordert, dass die Rückrufsignatur sein muss callback(error,result).


1
Welchen Vorteil hat dies gegenüber nativen Versprechen oder gegenüber den obigen Antworten?
Benjamin Gruenbaum

Was meinst du mit Native Promisify?
Loretoparisi


ah ja natürlich :). Nur und Beispiel, um die Grundidee zu zeigen. In der Tat können Sie sehen, wie selbst die native erfordert, dass die Funktionssignatur wie definiert definiert (err, value) => ...sein muss, oder Sie müssen eine benutzerdefinierte definieren (siehe Benutzerdefinierte versprochene Funktionen). Vielen Dank, gute 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))); };würde das gleiche tun wie Sie und es ist viel einfacher.
Patrick Roberts

1

Im Folgenden wird beschrieben, wie eine Funktion (Callback-API) in ein Versprechen konvertiert werden kann.

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

Es ist ungefähr 5 Jahre zu spät, aber ich wollte hier meine Promesify-Version veröffentlichen, die Funktionen aus der Callbacks-API übernimmt und sie in Versprechen umwandelt

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

Schauen Sie sich diese sehr einfache Version hier an: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a


1
Das ist kein Versprechen, es verkettet nicht, behandelt Fehler, die im Rückruf ausgelöst wurden, oder akzeptiert dann einen zweiten Parameter in ...
Benjamin Gruenbaum
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.