Jak przekonwertować istniejący interfejs API wywołania zwrotnego na obietnice?

Mar 20 2014

Chcę pracować z obietnicami, ale mam API wywołania zwrotnego w formacie:

1. Ładowanie DOM lub inne jednorazowe zdarzenie:

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

};

2. Zwykłe wywołanie zwrotne:

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

3. Wywołanie zwrotne w stylu węzła („nodeback”):

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

4. Cała biblioteka z wywołaniami zwrotnymi w stylu węzłów:

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

Jak pracować z API w obietnicach, jak to „obiecać”?

Odpowiedzi

769 BenjaminGruenbaum Mar 20 2014 at 05:47

Obietnice mają status, zaczynają się jako oczekujące i mogą zostać ustalone na:

  • spełnione, co oznacza, że ​​obliczenia zakończyły się pomyślnie.
  • odrzucone, co oznacza, że ​​obliczenia nie powiodły się.

Obietnica, że ​​funkcje zwracające nigdy nie powinny zwracać, zamiast tego powinny zwracać odrzucenia. Wyrzucenie z funkcji zwracającej obietnicę zmusi cię do użycia zarówno a, jak } catch { i a .catch. Osoby korzystające z obiecanych interfejsów API nie spodziewają się rzucania obietnic. Jeśli nie masz pewności, jak działają asynchroniczne interfejsy API w JS - najpierw zapoznaj się z tą odpowiedzią .

1. Ładowanie DOM lub inne jednorazowe zdarzenie:

Tak więc tworzenie obietnic ogólnie oznacza określenie, kiedy się rozliczają - to znaczy, kiedy przechodzą do fazy wypełnionej lub odrzuconej, aby wskazać, że dane są dostępne (i można uzyskać do nich dostęp .then).

Dzięki nowoczesnym implementacjom obietnic, które obsługują Promisekonstruktora, jak natywne obietnice ES6:

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

Następnie wykorzystasz wynikową obietnicę w następujący sposób:

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

Z bibliotekami, które obsługują deferred (użyjmy $ q w tym przykładzie tutaj, ale użyjemy również jQuery później):

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

Lub za pomocą jQuery, takiego jak API, przechwytywanie zdarzenia, które ma miejsce raz:

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

2. Zwykłe wywołanie zwrotne:

Te interfejsy API są dość powszechne, ponieważ cóż… wywołania zwrotne są powszechne w JS. Spójrzmy na typowy przypadek posiadania onSuccessi onFail:

function getUserData(userId, onLoad, onFail) { …

Dzięki nowoczesnym implementacjom obietnic, które obsługują Promisekonstruktora, jak natywne obietnice ES6:

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

Z bibliotekami, które obsługują deferred (użyjmy jQuery w tym przykładzie tutaj, ale użyliśmy również $ q powyżej):

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

jQuery oferuje również $.Deferred(fn)formularz, który ma tę zaletę, że pozwala nam napisać wyrażenie, które bardzo dokładnie naśladuje new Promise(fn)formularz, w następujący sposób:

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

Uwaga: tutaj wykorzystujemy fakt, że jQuery odroczone resolvei rejectmetody są „odłączalne”; to znaczy. są one powiązane z wystąpieniem jQuery.Deferred (). Nie wszystkie biblioteki oferują tę funkcję.

3. Wywołanie zwrotne w stylu węzła („nodeback”):

Wywołania zwrotne w stylu węzła (nodeback) mają określony format, w którym wywołania zwrotne są zawsze ostatnim argumentem, a ich pierwszym parametrem jest błąd. Najpierw obiecajmy ręcznie:

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

Do:

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

Z odroczonymi możesz wykonać następujące czynności (użyjmy Q w tym przykładzie, chociaż Q obsługuje teraz nową składnię, którą powinieneś preferować ):

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

Ogólnie rzecz biorąc, nie powinieneś zbytnio obiecywać rzeczy ręcznie, większość obiecujących bibliotek zaprojektowanych z myślą o Node, a także natywnych obietnic w Node 8+ ma wbudowaną metodę obiecywania nodebacków. Na przykład

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

4. Cała biblioteka z wywołaniami zwrotnymi w stylu węzłów:

Nie ma tu złotej zasady, obiecujesz im jeden po drugim. Jednak niektóre obiecujące implementacje pozwalają robić to zbiorczo, na przykład w Bluebird, konwersja nodeback API do obiecującego API jest tak prosta, jak:

Promise.promisifyAll(API);

Lub z natywnymi obietnicami w 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}), {});

Uwagi:

  • Oczywiście, gdy jesteś w .thentreserze, nie musisz niczego obiecywać. Zwrócenie obietnicy od .thenopiekuna rozstrzygnie lub odrzuci obietnicę o wartości. Rzucanie przez przewodnika .thenjest również dobrą praktyką i odrzuca obietnicę - to słynne obietnice dotyczące bezpieczeństwa rzutu.
  • W rzeczywistym onloadprzypadku, należy użyć addEventListenerzamiast onX.
58 efkan Jan 02 2017 at 20:19

Dziś mogę używać Promisew Node.jsjako zwykłej metody Javascript.

Prosty i podstawowy przykład, aby Promise(ze sposobem KISS ):

Zwykły JavaScript asynchroniczny kod API:

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

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

    successCallback( number / divider )

}

Promise Kod JavaScript Async API:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}

(Polecam odwiedzenie tego pięknego źródła )

Również Promisemożna stosować łączne async\awaitsię ES7, aby czekać przepływu programu na fullfiledskutek tak:

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

Inne użycie z tym samym kodem przy użyciu .then()metody

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

Promisemoże być również używany na dowolnej platformie, która jest oparta na node.js podobnego react-native.

Bonus : metoda hybrydowa
(zakłada się, że metoda wywołania zwrotnego ma dwa parametry jako błąd i wynik)

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 )

     })

}

Powyższa metoda może odpowiedzieć na wynik dla staromodnych połączeń zwrotnych i obietnic.

Mam nadzieję że to pomoże.

35 SivaKannan Aug 11 2017 at 18:31

Przed konwersją funkcji zgodnie z obietnicą w 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)
})

Po konwersji

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

W przypadku, gdy musisz obsłużyć wiele żądań

var allRequests = [];
allRequests.push(requestWrapper('http://localhost:8080/promise_request/1')) 
allRequests.push(requestWrapper('http://localhost:8080/promise_request/2'))
allRequests.push(requestWrapper('http://localhost:8080/promise_request/5'))    

Promise.all(allRequests).then(function (results) {
  console.log(results);//result will be array which contains each promise response
}).catch(function (err) {
  console.log(err)
});
23 Leo Jan 14 2015 at 11:15

Nie sądzę, aby window.onloadsugestia @Benjamina działała cały czas, ponieważ nie wykrywa, czy jest wywoływana po załadowaniu. Wiele razy mnie to ugryzło. Oto wersja, która zawsze powinna działać:

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);
15 GianMarco May 31 2017 at 13:46

Node.js 8.0.0 zawiera nowy util.promisify()interfejs API, który pozwala standardowym funkcjom wywołań zwrotnych Node.js zawrzeć funkcję, która zwraca Obietnicę. Przykładowe użycie util.promisify()jest pokazane poniżej.

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

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

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

Zobacz Ulepszona obsługa obietnic

14 Bruno May 16 2017 at 12:35

W wersji Kandydat do wydania dla Node.js 8.0.0 jest nowe narzędzie util.promisify(pisałem o util.promisify ), które zawiera zdolność obiecywania dowolnej funkcji.

Nie różni się zbytnio od podejść sugerowanych w innych odpowiedziach, ale ma tę zaletę, że jest metodą podstawową i nie wymaga dodatkowych zależności.

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

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

Następnie masz readFilemetodę, która zwraca wartość natywną Promise.

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

Możesz używać natywnych obietnic JavaScript w Node JS.

Link do My Cloud 9: https://ide.c9.io/adx2803/native-promises-in-node

/**
* Created by dixit-lab on 20/6/16.
*/

var express = require('express');
var request = require('request');   //Simplified HTTP request client.


var app = express();

function promisify(url) {
    return new Promise(function (resolve, reject) {
        request.get(url, function (error, response, body) {
            if (!error && response.statusCode == 200) {
                resolve(body);
            }
            else {
                reject(error);
            }
        })
    });
}

//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
    //get the post with post id 100
    promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
        var obj = JSON.parse(result);
        return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
    })
    .catch(function (e) {
        console.log(e);
    })
    .then(function (result) {
        res.end(result);
    })
})

var server = app.listen(8081, function () {
    var host = server.address().address
    var port = server.address().port

    console.log("Example app listening at http://%s:%s", host, port)
})

//run webservice on browser : http://localhost:8081/listAlbums
7 daviddavis Nov 28 2016 at 10:07

W przypadku zwykłego, starego javaScript, oto rozwiązanie obiecujące wywołanie zwrotne interfejsu API.

function get(url, callback) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url);
        xhr.addEventListener('readystatechange', function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    console.log('successful ... should call callback ... ');
                    callback(null, JSON.parse(xhr.responseText));
                } else {
                    console.log('error ... callback with error data ... ');
                    callback(xhr, null);
                }
            }
        });
        xhr.send();
    }

/**
     * @function promisify: convert api based callbacks to promises
     * @description takes in a factory function and promisifies it
     * @params {function} input function to promisify
     * @params {array} an array of inputs to the function to be promisified
     * @return {function} promisified function
     * */
    function promisify(fn) {
        return function () {
            var args = Array.prototype.slice.call(arguments);
            return new Promise(function(resolve, reject) {
                fn.apply(null, args.concat(function (err, result) {
                    if (err) reject(err);
                    else resolve(result);
                }));
            });
        }
    }

var get_promisified = promisify(get);
var promise = get_promisified('some_url');
promise.then(function (data) {
        // corresponds to the resolve function
        console.log('successful operation: ', data);
}, function (error) {
        console.log(error);
});
6 JasonLoveman Apr 08 2015 at 01:30

Biblioteka Q autorstwa kriskowala zawiera funkcje zwrotne na obietnicę. Taka metoda:

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

można przekonwertować za pomocą Q.ninvoke

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

Jeśli masz kilka funkcji, które odbierają wywołanie zwrotne i chcesz, aby zamiast tego zwracały obietnicę, możesz użyć tej funkcji do wykonania konwersji.

function callbackToPromise(func){

    return function(){

        // change this to use what ever promise lib you are using
        // In this case i'm using angular $q that I exposed on a util module

        var defered = util.$q.defer();

        var cb = (val) => {
            defered.resolve(val);
        }

        var args = Array.prototype.slice.call(arguments);
        args.push(cb);    
        func.apply(this, args);

        return defered.promise;
    }
}
4 PaulSpaulding Apr 12 2017 at 23:48

W węźle v7.6 +, który ma wbudowane obietnice i asynchroniczne:

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

Jak używać:

let readdir = require('fs').readdir;
let promisify = require('./promisify');
let readdirP = promisify(readdir);

async function myAsyncFn(path) {
    let entries = await readdirP(path);
    return entries;
}
3 DoAsync Oct 13 2017 at 05:19

W Node.js 8 możesz obiecywać metody obiektów w locie za pomocą tego modułu npm:

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

Używa util.promisify i serwerów proxy, dzięki czemu obiekty pozostają niezmienione. Memoizacja odbywa się również za pomocą WeakMaps). Oto kilka przykładów:

Z obiektami:

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

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

Z funkcjami:

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

Możesz nawet użyć natywnego calli applypowiązać jakiś kontekst:

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

Możesz użyć natywnej Promise w ES6, na przykład w przypadku 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
    });

}

W tym przykładzie Obietnica nie ma powodu do niepowodzenia, więc reject()nigdy nie jest nazywana.

2 jituanlin Jul 30 2017 at 20:39

Funkcja stylu wywołania zwrotnego zawsze wygląda tak (prawie wszystkie funkcje w node.js mają ten styl):

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

Ten styl ma tę samą funkcję:

  1. funkcja zwrotna jest przekazywana przez ostatni argument.

  2. funkcja zwrotna zawsze akceptuje obiekt błędu jako pierwszy argument.

Możesz więc napisać funkcję do konwersji funkcji w tym stylu w następujący sposób:

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

Aby uzyskać bardziej zwięzły, powyższy przykład użył ramda.js. Ramda.js to doskonała biblioteka do programowania funkcjonalnego. W powyższym kodzie użyliśmy go zastosuj (jak javascript function.prototype.apply) i dołącz (jak javascript function.prototype.push). Możemy więc teraz przekonwertować funkcję stylu wywołania zwrotnego na funkcję obiecującą styl:

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

toPromise i checkErr funkcja jest właścicielem przez berserk biblioteki, to funkcjonalna biblioteka programistyczna widelec przez ramda.js (tworzenie przeze mnie).

Mam nadzieję, że ta odpowiedź jest dla Ciebie przydatna.

2 onmyway133 Oct 09 2018 at 20:35

Możesz zrobić coś takiego

// @flow

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

export default toPromise

Następnie użyj go

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

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

es6-promisify konwertuje funkcje oparte na wywołaniach zwrotnych na funkcje oparte na Promise.

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

const promisedFn = promisify(callbackedFn, args);

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

1 loretoparisi Nov 30 2017 at 06:34

Moja wersja callbackfunkcji promisify to Pfunkcja:

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

PFunkcja wymaga, aby podpis zwrotna musi być callback(error,result).

1 Mzndako Jun 21 2019 at 17:38

Poniżej znajduje się implementacja, w jaki sposób funkcję (API zwrotne) można przekształcić w obietnicę.

function promisify(functionToExec) {
  return function() {
    var array = Object.values(arguments);
    return new Promise((resolve, reject) => {
      array.push(resolve)
      try {
         functionToExec.apply(null, array);
      } catch (error) {
         reject(error)
      }
    })
  }
}

// USE SCENARIO

function apiFunction (path, callback) { // Not a promise
  // Logic
}

var promisedFunction = promisify(apiFunction);

promisedFunction('path').then(()=>{
  // Receive the result here (callback)
})

// Or use it with await like this
let result = await promisedFunction('path');

1 JosiahNyarega Sep 25 2020 at 01:28

Z przyszłości 😄

Prosta funkcja ogólna, której zwykle używam.

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

Jak tego użyć

   promisify(fn, arg1, arg2)

Prawdopodobnie nie szukasz tej odpowiedzi, ale pomoże to zrozumieć wewnętrzne działanie dostępnych narzędzi