wake-up-neo.com

Wie konvertiere ich eine vorhandene Callback-API in Versprechen?

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

1. DOM laden oder 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 Node-Stil ("Nodeback"):

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

4. Eine ganze Bibliothek mit Callbacks im Node-Stil:

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 es?

666

Versprechungen haben Status, sie beginnen als ausstehend und können sich auf Folgendes beschränken:

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

Versprechen, Funktionen zurückzugeben sollte niemals werfen, sie sollten stattdessen Ablehnungen zurückgeben. Wenn Sie von einer Funktion zur Rückgabe eines Versprechens werfen, müssen Sie sowohl einen } catch { als auch einen .catch verwenden. Leute, die versprochene APIs verwenden, erwarten nicht, dass Versprechen geworfen werden. Wenn Sie sich nicht sicher sind, wie asynchrone APIs in JS funktionieren, siehe diese Antwort zuerst.

1. DOM laden oder anderes einmaliges Ereignis:

Wenn Sie also Versprechungen erstellen, müssen Sie im Allgemeinen angeben, wann sie abgerechnet werden. Dies bedeutet, dass Sie in die erfüllte oder abgelehnte Phase wechseln, um anzuzeigen, dass die Daten verfügbar sind (und mit .then abgerufen werden können).

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

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 Deferred unterstützen (Verwenden wir für dieses Beispiel $ q, aber jQuery wird später ebenfalls verwendet):

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

Oder mit einer jQuery-ähnlichen API:

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 allgemeinen Fall an, in dem onSuccess und onFail vorkommen:

function getUserData(userId, onLoad, onFail) { …

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

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

Mit Bibliotheken, die Deferred unterstützen (Verwenden wir für dieses Beispiel jQuery, aber wir haben auch oben $ 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 sehr genau emuliert:

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

Hinweis: Hier wird die Tatsache ausgenutzt, dass die Methoden resolve und reject eines jQuery Deferred "detachable" sind. dh Sie sind an die Instanz eines jQuery.Deferred () gebunden. Nicht alle Bibliotheken bieten diese Funktion an.

3. Rückruf im Node-Stil ("Nodeback"):

Callbacks im Knotenstil (Nodebacks) haben ein bestimmtes Format, bei dem Callbacks immer das letzte Argument und der erste Parameter ein Fehler sind. Lassen Sie uns zunächst einen 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 verzögerten Anweisungen können Sie Folgendes tun (in diesem Beispiel verwenden wir Q, 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 Versprechen-Bibliotheken, die unter Berücksichtigung von Node entwickelt wurden, sowie native Versprechen in Node 8+ verfügen über eine integrierte Methode zum Versprechen 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 Callbacks im Node-Stil:

Hier gibt es keine goldene Regel, Sie versprechen sie eins nach dem anderen. Bei einigen Versprechungsimplementierungen ist dies jedoch in großen Mengen möglich, z. B. in Bluebird. Die Konvertierung einer Nodeback-API in eine Versprechungs-API ist so einfach wie:

Promise.promisifyAll(API);

Oder mit nativen Versprechungen in Knoten :

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 .then -Handler befinden, brauchen Sie natürlich keine Zusagen zu machen. Das Zurückgeben eines Versprechens von einem .then -Handler wird mit dem Wert dieses Versprechens aufgelöst oder abgelehnt. Das Werfen von einem .then -Handler ist ebenfalls eine gute Übung und lehnt das Versprechen ab - dies ist das berühmte Versprechen für die Wurfsicherheit.
  • In einem tatsächlichen onload Fall sollten Sie addEventListener anstelle von onX verwenden.
689

Heute kann ich Promise in Node.js als einfache Javascript-Methode verwenden.

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

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 den Besuch diese schöne Quelle )

Außerdem kann Promise zusammen mit async\await in ES7 verwendet werden, damit der Programmablauf auf ein fullfiled -Ergebnis wie das folgende wartet:

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

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

Bonus : Eine hybride Methode
(Es wird angenommen, dass die Rückrufmethode zwei Parameter als Fehler und Ergebnis hat.)

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 Rückruf- und Versprechungsnutzungen reagieren.

Hoffe das hilft.

48
efkan

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 der Konvertierung

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

Ich denke nicht, dass der window.onload Vorschlag 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);
21
Leo

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

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

12

In Release Candidate für Node.js 8.0.0 gibt es ein neues Hilfsprogramm, util.promisify (ich habe über til.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 readFile -Methode, die eine native Promise zurückgibt.

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

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

Link zu meinem Cloud 9-Code: 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
5
Apoorv

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) {
});
4
Jason Loveman

Unter Knoten v7.6 +, der Versprechen und Async 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
Paul Spaulding

Mit einfachem, altem Vanilla-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);
});
2
daviddavis

In Node.js 8 können Sie Objektmethoden im laufenden Betrieb mit diesem npm-Modul versprechen :

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

Es verwendet util.promisify und Proxies , damit Ihre Objekte unverändert bleiben. Das Merken erfolgt auch mit 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 call und apply verwenden, um einen bestimmten Kontext zu binden:

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

Wenn Sie einige Funktionen haben, die einen Rückruf annehmen, und Sie möchten, dass sie 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;
    }
}
2
user1852503

Die Callback-Stil -Funktion sieht immer so aus (fast alle Funktionen in node.js haben diesen Stil):

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

Dieser Stil hat die gleiche Funktion:

  1. die Rückruffunktion wird durch das letzte 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 wird im obigen Beispiel ramda.js verwendet. Ramda.js ist eine hervorragende Bibliothek für die funktionale Programmierung. Im obigen Code haben wir verwendet, dass angewendet (wie Javascript function.prototype.apply) und angehängt (wie Javascript function.prototype.Push) wird. So konnten wir die a-Callback-Style-Funktion jetzt in eine Versprechen-Style-Funktion konvertieren:

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

toPromise und checkErr Funktion ist Eigentum von berserker = Bibliothek, es ist eine funktionale Programmierbibliotheksgabel von ramda.js (von mir erstellt).

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

1
jituanlin

Sie können native Promise 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 Promise keinen Grund, fehlzuschlagen. Daher wird reject() nie aufgerufen.

1
Nicolas Zozol

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

Meine versprochene Version einer callback -Funktion ist die P -Funktion:

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

Für die Funktion P muss die Rückrufsignatur callback(error,result) sein.

0
loretoparisi

Im Folgenden wird beschrieben, wie eine Funktion (Callback-API) in ein Versprechen umgewandelt 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');

0
Mzndako

es6-promisify konvertiert Callback-basierte Funktionen in Promise-basierte Funktionen.

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

const promisedFn = promisify(callbackedFn, args);

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

0