Jak przekonwertować istniejące API zwrotne na obietnice?


721

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

1. Ładowanie DOM lub inne jednorazowe zdarzenie:

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

};

2. Zwykły oddzwonienie:

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

3. Oddzwanianie 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ła:

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

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


Opublikowałem własną odpowiedź, ale odpowiedzi na te pytania dotyczące konkretnej biblioteki lub w większej liczbie przypadków i edycji są bardzo mile widziane.
Benjamin Gruenbaum,

@Bergi To ciekawy pomysł, próbowałem udzielić ogólnej odpowiedzi, która wykorzystuje dwa wspólne podejścia (konstruktor obietnicy i odroczony obiekt). Próbowałem podać dwie alternatywy w odpowiedziach. Zgadzam się, że RTFMing rozwiązuje ten problem, ale często spotykamy się z tym problemem zarówno tutaj, jak i w narzędziu do śledzenia błędów, więc pomyślałem, że istnieje „pytanie kanoniczne” - myślę, że RTFMing rozwiązuje około 50% problemów w tagu JS: D Jeśli masz ciekawy wgląd w odpowiedź lub edycję, która byłaby bardzo mile widziana.
Benjamin Gruenbaum,

Czy utworzenie new Promisedodatkowego znaczącego narzutu? Chcę zawinąć wszystkie moje synchroniczne funkcje Noje.js w obietnicę, aby usunąć cały kod synchroniczny z mojej aplikacji Node, ale czy to najlepsza praktyka? Innymi słowy, funkcja, która akceptuje argument statyczny (np. Ciąg znaków) i zwraca wynik obliczony, czy mam zawinąć to w obietnicę? ... Przeczytałem gdzieś, że nie powinieneś mieć żadnego kodu synchronicznego w Nodejs.
Ronnie Royston

1
@RonRoyston nie, nie jest dobrym pomysłem owijanie połączeń synchronicznych obietnicami - tylko połączenia asynchroniczne, które mogą wykonywać
operacje

Odpowiedzi:


743

Obietnice mają swój stan, zaczynają się jako oczekujące i mogą ustąpić:

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

Funkcje obiecujące powracające funkcje nigdy nie powinny rzucać , zamiast tego powinny zwracać odrzucenia. Wyrzucenie z funkcji powrotu obietnicy zmusi cię do użycia zarówno a, jak } catch { i a .catch. Osoby korzystające z obiecanych interfejsów API nie oczekują, że obietnice zostaną rzucone. Jeśli nie masz pewności, jak działają asynchroniczne interfejsy API w JS - zapoznaj się z tą odpowiedzią najpierw .

1. Ładowanie DOM lub inne jednorazowe zdarzenie:

Tak więc tworzenie obietnic zazwyczaj oznacza określenie, kiedy się rozliczają - to znaczy, kiedy przechodzą do fazy speł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 wspierają Promisekonstruktora, podobnie jak natywne obietnice ES6:

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

Następnie skorzystasz z wynikowej obietnicy w następujący sposób:

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

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

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

Lub za pomocą interfejsu jQuery, takiego jak interfejs API, umożliwiając jednokrotne zdarzenie:

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

2. Zwykły oddzwonienie:

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

function getUserData(userId, onLoad, onFail) { 

Dzięki nowoczesnym implementacjom obietnic, które wspierają Promisekonstruktora, podobnie jak natywne obietnice ES6:

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

Z bibliotekami, które obsługują odroczenie (użyjmy tutaj jQuery dla tego przykładu, 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 emuluje 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: W tym miejscu wykorzystujemy fakt, że odroczone resolvei rejectmetody jQuery są „odłączalne”; to znaczy. są powiązane z instancją jQuery.Deferred (). Nie wszystkie biblioteki oferują tę funkcję.

3. Oddzwanianie w stylu węzła („nodeback”):

Odwołania zwrotne w stylu węzła (nodebacks) mają określony format, w którym wywołania zwrotne są zawsze ostatnim argumentem, a jego pierwszym parametrem jest błąd. Obiecajmy najpierw 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);
        });
    });
}

W przypadku odroczenia 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, większość bibliotek obietnic, które zostały zaprojektowane z myślą o Węzle, a także natywne obietnice w Node 8+ mają wbudowaną metodę obiecujących plecaków węzłó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ła:

Nie ma tutaj złotej reguły, obiecasz je jeden po drugim. Jednak niektóre implementacje obietnicy pozwalają to zrobić zbiorczo, na przykład w Bluebird, konwersja interfejsu API nodeback do interfejsu API obietnicy jest tak prosta, jak:

Promise.promisifyAll(API);

Lub z rodzimymi obietnicami w węźle :

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 przewodniku .then, nie musisz obiecywać rzeczy. Zwrócenie obietnicy od .thenopiekuna rozwiązuje lub odrzuca wartość tej obietnicy. Rzucanie od przewodnika .thenjest również dobrą praktyką i odrzuca obietnicę - jest to słynne obietnica bezpieczeństwa rzucania.
  • W rzeczywistości onloadpowinieneś użyć addEventListenerzamiast onX.

Benjamin, zaakceptowałem twoje zaproszenie do edycji i dodałem kolejny przykład jQuery do przypadku 2. Będzie wymagał przeglądu przez recenzentów, zanim się pojawi. Mam nadzieję że ci się spodoba.
Roamer-1888

@ Roamer-1888 został odrzucony, ponieważ nie widziałem go i nie zaakceptowałem na czas. Jeśli chodzi o to, co warto, nie sądzę, aby ten dodatek był zbyt istotny, choć użyteczny.
Benjamin Gruenbaum

2
Benjamin, czy też nie resolve()i reject()są zapisywane do wielokrotnego użytku, zaryzykuję, że mój zaproponował edycji jest istotne, ponieważ oferuje przykład jQuery formularza $.Deferred(fn), który w przeciwnym razie brakuje. Jeśli podany jest tylko jeden przykład jQuery, sugeruję, że powinien on mieć taką formę, a nie var d = $.Deferred();itp., Ponieważ należy zachęcać ludzi do używania często zaniedbanej $.Deferred(fn)formy, a także w takiej odpowiedzi stawia jQuery bardziej na równi z biblioteki, które używają Revealing Constructor Pattern .
Roamer-1888

Heh, aby być w 100% uczciwym, nie wiedziałem, że pozwala ci to zrobić jQuery $.Deferred(fn), jeśli edytujesz to zamiast w istniejącym przykładzie w ciągu następnych 15 minut, jestem pewien, że mogę spróbować zatwierdzić go na czas :)
Benjamin Gruenbaum,

7
To świetna odpowiedź. Możesz go zaktualizować, wspominając również util.promisify, że Node.js zamierza dodać do swojego rdzenia, począwszy od wersji RC 8.0.0. Jego działanie nie różni się zbytnio od Bluebird Promise.promisify, ale ma tę zaletę, że nie wymaga dodatkowych zależności, na wypadek, gdybyś chciał tylko natywną obietnicę. Napisałem post na blogu o util.promisify dla każdego, kto chce przeczytać więcej na ten temat.
Bruno

55

Dziś mogę użyć PromisewNode.js postaci zwykłego metodą Javascript.

Prosty i podstawowy przykład Promise(z metodą KISS ):

Zwykły kod JavaScript Async 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 odwiedzić to piękne źródło )

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

Kolejne 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.

Premia : 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 wyniki w przypadku zwrotów w starym stylu i obietnic.

Mam nadzieję że to pomoże.


3
Nie wydają się pokazywać, jak przejść na obietnice.
Dmitri Zaitsev

33

Przed konwersją funkcji jako obietnicy 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)
})

Musisz obsłużyć wiele wniosków

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

Nie sądzę, aby window.onloadsugestia @Benjamin działała cały czas, ponieważ nie wykrywa, czy zostanie wywołana po załadowaniu. Byłem przez to ugryziony wiele razy. 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);

1
czy oddział „już ukończony” nie powinien używać setTimeout(resolve, 0)(lub setImmediate, jeśli jest dostępny), aby upewnić się, że jest wywoływany asynchronicznie?
Alnitak

5
@Alnitak Dzwonienie resolvesynchroniczne jest w porządku. Procedury obsługi obietnicy thengwarantowane przez strukturę, która ma być wywoływana asynchronicznie , niezależnie od tego, czy resolvejest wywoływana synchronicznie.
Jeff Bowman,

15

Node.js 8.0.0 zawiera nowy util.promisify()interfejs API, który umożliwia pakowanie standardowych interfejsów API stylu wywołania zwrotnego Node.js w funkcję zwracającą obietnicę. Przykładowe zastosowanie util.promisify()pokazano poniżej.

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

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

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

Zobacz Ulepszone wsparcie dla obietnic


2
Istnieją już dwie odpowiedzi opisujące to, po co publikować trzecią?
Benjamin Gruenbaum,

1
Tylko dlatego, że ta wersja węzła została już wydana, a ja zgłosiłem „oficjalny” opis funkcji i link.
Gian Marco Gherardi,

14

W wersji Release Candidate dla Node.js 8.0.0 pojawiło się nowe narzędzie util.promisify(pisałem o util.promisify ), które zawiera w sobie zdolność obiecania dowolnej funkcji.

Nie różni się zbytnio od podejść sugerowanych w innych odpowiedziach, ale ma tę zaletę, że jest podstawową metodą 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 natywną Promise.

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

1
Hej, ja (OP) faktycznie zasugerowałem util.promisifydwa razy (w 2014 r., Kiedy to pytanie zostało napisane, i kilka miesięcy temu - o co nalegałem jako główny członek Node i jest to bieżąca wersja, którą mamy w Node). Ponieważ nie jest jeszcze publicznie dostępny - nie dodałem go jeszcze do tej odpowiedzi. Bylibyśmy bardzo wdzięczni za opinie na temat użytkowania i poznawanie niektórych pułapek, aby mieć lepsze dokumenty do wydania :)
Benjamin Gruenbaum

1
Ponadto możesz omówić niestandardową flagę promującą util.promisifyw swoim wpisie na blogu :)
Benjamin Gruenbaum

@BenjaminGruenbaum Czy masz na myśli fakt, że używając util.promisify.customsymbolu można zastąpić wynik funkcji util.promisify? Szczerze mówiąc, była to celowa chybienie, ponieważ nie jestem jeszcze w stanie znaleźć przydatnego przypadku użycia. Może mógłbyś podać mi jakieś dane?
Bruno

1
Oczywiście, rozważ interfejsy API podobne fs.existslub interfejsy API, które nie są zgodne z konwencją dotyczącą węzłów - bluebird Promise.promisify popełni je źle, ale util.promisifypoprawi je.
Benjamin Gruenbaum,

7

Z Node JS możesz korzystać z natywnych obietnic JavaScript.

Link do kodu 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

W zwykłym starym javaScript waniliowym oto rozwiązanie, które obiecuje 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

Biblioteka Q firmy kriskowal zawiera funkcje zwrotne do obietnicy. Metoda taka jak ta:

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

1
Odpowiedź kanoniczna już wspomina Q.denodeify. Czy musimy podkreślać pomocników biblioteki?
Bergi,

3
uznałem to za użyteczne jako google o promowaniu w leadach Q
Ed Sykes

4

Jeśli masz kilka funkcji, które odbierają połączenie zwrotne i chcesz, aby zwróciły obietnicę, możesz użyć tej funkcji do 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

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

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

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

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

Wykorzystuje util.promisify i proxy , aby twoje obiekty pozostały niezmienione. Zapamiętywanie odbywa się również przy użyciu 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

Możesz użyć natywnej obietnicy 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, by zawieść, więc reject()nigdy nie jest nazywana.


2

Funkcja stylu wywołania zwrotnego jest zawsze taka (prawie wszystkie funkcje w node.js to 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 przyjmuje obiekt błędu jako pierwszy argument.

Możesz napisać funkcję do konwersji funkcji w tym stylu:

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

Dla większej zwięzłości powyższy przykład wykorzystał 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 dopisz (jak javascript function.prototype.push). Możemy więc teraz przekonwertować funkcję stylu wywołania zwrotnego na funkcję stylu obietnicy:

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

Funkcja toPromise i checkErr jest własnością biblioteki berserk , jest to funkcjonalny rozwidlenie biblioteki programowania autorstwa ramda.js (utworzone przeze mnie).

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


2

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

2
Hej, nie jestem pewien, co to dodaje do istniejących odpowiedzi (może to wyjaśnić?). Ponadto nie ma potrzeby próbowania / chwytania w konstruktorze obietnicy (robi to automatycznie dla Ciebie). Jest również jasne, co działa to działa dla (które wywołują zwrotnego z jednym argumentem na sukces Jak obsługiwane są błędy?)
Benjamin Gruenbaum


1

Moja wersja promisify callbackfunkcji 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))

Ta Pfunkcja wymaga podpisu wywołania zwrotnego callback(error,result).


1
Jakie to ma przewagę nad natywną obietnicą lub nad powyższymi odpowiedziami?
Benjamin Gruenbaum

Co masz na myśli mówiąc o „native promisify”?
loretoparisi


ah tak oczywiście :). Tylko i przykład, aby pokazać podstawowy pomysł. W rzeczywistości możesz zobaczyć, jak nawet natywny wymaga, aby podpis funkcji musiał być zdefiniowany podobnie (err, value) => ...lub musisz zdefiniować podpis niestandardowy (zobacz Niestandardowe funkcje obiecane). Dziękuję, dobra 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))); };zrobiłby to samo co twoje i jest o wiele prostsze.
Patrick Roberts,

1

Poniżej przedstawiono implementację sposobu, w jaki funkcja (API zwrotne) może zostać przekonwertowana na 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');

-2

To jest jak 5 lat spóźnienia, ale chciałem opublikować tutaj moją wersję promesify, która przejmuje funkcje z API zwrotnego i zamienia je w obietnice

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

Zobacz tę bardzo prostą wersję tutaj: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a


1
To nie jest obietnica, nie łączy się, nie radzi sobie z błędami zgłaszanymi w wywołaniu zwrotnym lub akceptuje drugi parametr w tym czasie ...
Benjamin Gruenbaum
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.