Hogyan készítsünk ígéretet egy visszahívási funkcióból a JavaScript-ben

A háttérfejlesztők állandóan kihívásokba ütköznek, miközben alkalmazásokat építenek vagy tesztelnek kódot. Fejlesztőként, aki meglehetősen új és megismeri ezeket a kihívásokat, soha nem találkoztam gyakrabban - vagy emlékezetesebb módon - kihívással vagy kényelmetlenséggel, mint a visszahívási funkciókkal.

Nem fogok túl mélyen elmélyülni a visszahívás részleteiben, előnyeiben és hátrányaiban vagy alternatíváiban, például az ígéretekben és az aszinkronban / várakozásban. Élénkebb magyarázatért tekintse meg ezt a cikket, amely alaposan elmagyarázza őket.

Visszahívás a pokolba

A visszahívások a JavaScript-ek hasznos funkciói, amelyek lehetővé teszik az aszinkron hívások kezdeményezését. Ezek olyan függvények, amelyeket általában második paraméterként adnak át egy másik függvénynek, amely adatok beolvasása vagy egy I / O művelet végrehajtása, amely időbe telik.

Próbáljon meg például API-hívást kezdeményezni a requestmodulhoz vagy csatlakozás egy MongoDB adatbázishoz. De mi van akkor, ha mindkét hívás függ egymástól? Mi van, ha a beolvasott adat az a MongoDB URL, amelyhez csatlakoznia kell?

Ezeket a hívásokat egymásba kell ágyaznia:

request.get(url, function(error, response, mongoUrl) { if(error) throw new Error("Error while fetching fetching data"); MongoClient.connect(mongoUrl, function(error, client) { if(error) throw new Error("MongoDB connection error"); console.log("Connected successfully to server"); const db = client.db("dbName"); // Do some application logic client.close(); }); });

Oké ... szóval hol a probléma? Nos, egyrészt a kód olvashatósága szenved ettől a technikától.

Elsőre jónak tűnhet, ha a kódbázis kicsi. De ez nem skálázódik jól, különösen, ha több réteggel megy mélyebben a beágyazott visszahívásokba.

Sok záró zárójel és göndör zárójel lesz a végén, amelyek összezavarják Önt és más fejlesztőket, függetlenül attól, hogy a kódja milyen formázott formában van. Van egy callbackhell nevű webhely, amely foglalkozik ezzel a problémával.

Hallom, hogy néhányan, köztük a naiv múltam, azt mondják, hogy tekerjem be egy asyncfunkció, majd await a visszahívási funkció. Ez egyszerűen nem működik.

Ha a visszahívásokat használó funkció után van valamilyen kódblokk, akkor az végrehajtja a kódblokkot, és NEM várja meg a visszahívást.

Itt van ez a hiba, amit korábban elkövettem:

var request = require('request'); // WRONG async function(){ let joke; let url = "//api.chucknorris.io/jokes/random" await request.get(url, function(error, response, data) { if(error) throw new Error("Error while fetching fetching data"); let content = JSON.parse(data); joke = content.value; }); console.log(joke); // undefined }; // Wrong async function(){ let joke; let url = "//api.chucknorris.io/jokes/random" request.get(url, await function(error, response, data) { if(error) throw new Error("Error while fetching fetching data"); let content = JSON.parse(data); joke = content.value; }); console.log(joke); // undefined };

Néhány tapasztaltabb fejlesztő azt mondhatja: „Csak használjon egy másik könyvtárat, amely ígéreteket használ ugyanarra a dologra, mint például az axios,vagy csak a letöltést használja ” . Persze, hogy tudok ebben a forgatókönyvben, de ez csak menekül a probléma elől.

Ezenkívül ez csak egy példa. Néha el lehet zárni egy olyan könyvtár használatával, amely alternatívák nélkül nem támogatja az ígéreteket. Mint például a szoftverfejlesztő készletek (SDK) használata az olyan platformokkal való kommunikációhoz, mint az Amazon Web Services (AWS), a Twitter vagy a Facebook.

Néha még a visszahívás használata is nagyon egyszerű hívás kezdeményezésére gyors I / O vagy CRUD művelettel rendben van, és más logika nem függ az eredményeitől. De korlátozhatja a futási környezet, mint például a Lambda függvényben, amely a fő szál befejeztével minden folyamatot elpusztít, függetlenül a nem teljes aszinkron hívásoktól.

1. megoldás (egyszerű): Használja a Node „util” modulját

A megoldás meglepően egyszerű. Még akkor is, ha kissé kényelmetlenül érzi magát az ígéretek gondolata a JavaScript-ben, szeretni fogja, hogyan oldhatja meg ezt a problémát azok használatával.

Amint Erop és Robin megjegyzéseiben rámutatott, a Nodejs 8-as és újabb verziói már támogatják a visszahívási funkciók ígéretekké alakítását a beépített util modul használatával.

const request = require('request'); const util = require('util'); const url = "//api.chucknorris.io/jokes/random"; // Use the util to promisify the request method const getChuckNorrisFact = util.promisify(request); // Use the new method to call the API in a modern then/catch pattern getChuckNorrisFact(url).then(data => { let content = JSON.parse(data.body); console.log('Joke: ', content.value); }).catch(err => console.log('error: ', err))

A fenti kód szépen megoldja a problémát az util.promisify használatávalmódszer elérhető a nodejs magkönyvtárából.

Csak annyit kell tennie, hogy argumentumként használja a visszahívás függvényt az util.promisify használatához és egy változó tárolásához. Az én esetemben ez a getChuckNorrisFact .

Ezután azt a változót használja függvényként, amelyet ígéretként használhat a .then () és a .catch () metódusokkal.

2. megoldás (érintett): A visszahívást ígéretté alakíthatja

Előfordul, hogy a request és util könyvtárak használata egyszerűen nem lehetséges, akár egy régi környezet / kódbázis miatt, akár a kliensoldali böngészőből történő kérések végrehajtása miatt, ígéretet kell tennie a visszahívási funkció köré.

Vegyük a fenti Chuck Norris példát, és tegyük ezt ígéretté.

var request = require('request'); let url = "//api.chucknorris.io/jokes/random"; // A function that returns a promise to resolve into the data //fetched from the API or an error let getChuckNorrisFact = (url) => { return new Promise( (resolve, reject) => { request.get(url, function(error, response, data){ if (error) reject(error); let content = JSON.parse(data); let fact = content.value; resolve(fact); }) } ); }; getChuckNorrisFact(url).then( fact => console.log(fact) // actually outputs a string ).catch( error => console.(error) );

A fenti kódban a visszahívás alapú requestfüggvényt egy Promise burkolóba tettem Promise( (resolve, reject) => { //callback function}). Ez a csomagoló lehetővé teszi számunkra, hogy a getChuckNorrisFactfüggvényt ígéretként hívjuk meg a .then()és .catch()metódusokkal. A getChuckNorrisFacthívásakor végrehajtja a kérést az API-nak, és megvárja a resolve()vagy egy reject()utasítás végrehajtását. A visszahívási funkcióban egyszerűen átadja a visszakeresett adatokat a feloldási vagy elutasítási módszereknek.

Miután az adatokat (ebben az esetben egy fantasztikus Chuck Norris tényt) lekérte és továbbította a felbontónak, getChuckNorrisFactvégrehajtja a then()módszert. Ez azt az eredményt adja vissza, amelyet a függvény belsejében felhasználhatthen() a kívánt logika elvégzéséhez - ebben az esetben a konzolon való megjelenítéshez.

Bővebben az MDN Web Docs-ban olvashat.