zynif Δημοσ. 14 Δεκεμβρίου 2017 Δημοσ. 14 Δεκεμβρίου 2017 Καλημέρα. Λοιπόν είμαι τελείως αρχάριος στα promises αλλά θελω να κάνω το εξης Θέλω οταν αποτυγχάνει το promise A να εκτελείται το promise B και αν αυτο αποτύχει τοτε να εκτελειται το promise C . Αν φυσικά πετύχει κάποιο promise τότε η ολη αλυσιδα θα πρέπει να σταματάει . Έχω γράψει το παρακάτω tryPromising(params) { var self = this; return new Promise(function (resolve, reject) { self.FirstPromise().then(responseA => { resolve({ success: 1, id: 'A' }); }).catch(errorA => { self.SecondPromise().then(responseB => { resolve({ success: 1, id: 'B' }); }).catch(errorB => { self.ThirdPromise().then(responseC => { resolve({ success: 1, id: 'C' }); }).catch(errorC => { reject({ success: -100 }); }); }); }) }); } Υπάρχει κάποιος καλύτερος τρόπος αν γνωρίζετε ; Ευχαριστώ
paparovic Δημοσ. 15 Δεκεμβρίου 2017 Δημοσ. 15 Δεκεμβρίου 2017 Άκυρο, έχω κάνει ένα λάθος - θα επανέλθω
paparovic Δημοσ. 15 Δεκεμβρίου 2017 Δημοσ. 15 Δεκεμβρίου 2017 Επανήλθα Καταρχάς μπράβο που ψάχνεσαι! Σου έφτιαξα μια όμορφη υλοποίηση για έμπνευση: /* The trick is that promises are eager - they start executing immediately. You want it sequentially. So, you need to have an object that creates them on demand. */ /* Wrapper example for getting promises "lazily" */ const leFactory = (function() { const list = []; // This is a private variable const pub = {}; // We'll return this one to make it public pub[Symbol.iterator] = function* () { // This lets us loop over the list later on while (list.length) { yield list.pop(); } }; // This is how you insert your "future" promises into the wrapper pub.add = function (what) { const f = () => { // Returns a FUNCTION that will create a promise WHEN invoked return new Promise(what); }; list.push(f); } return pub; }()); // This is an IFFE /* Feed for lazy Promises */ const promiseThatWillFail = function(resolve, reject) { reject('whoops') }; const promiseThatWillSucceed = function(resolve, reject) { resolve('Javascript FTW') }; const anotherFailureAsAFuturePromise = function(resolve, reject) { reject('Olympiakos') }; /* Lotto */ const shuffleArray = arr => arr.sort(() => Math.random() - 0.5); const promiseFeeds = shuffleArray([promiseThatWillFail, promiseThatWillSucceed, anotherFailureAsAFuturePromise]); /* Wrap'em by the function */ promiseFeeds.forEach((p) => { leFactory.add(p); }); /* Worker function */ async function tryDifferentStuffAndGiveMeAResult(leFactory) { let result; for (const p of leFactory) { try { const invokedPromise = p(); // Finally, this is where each promise gets created, sequentially result = await invokedPromise; if (result) { // `if` isn't really needed here, just for visual effect, safe to omit return result; // This stops the function and breaks the "chain" } } catch (e) { // Nothing to do here in respect with `result` console.log(`Rejected: ${e}`); // Just print what failed so that we can see it } } } /* Run to the hills */ tryDifferentStuffAndGiveMeAResult(leFactory) .then((result) => { console.log(`I did: ${result}`); console.log(`----||----`); }); Για να το τρέξεις εύκολα και γρήγορα, πήγαινε στο https://es6console.com και ενεργοποίησε όλα τα presets (εκτός από το react). (Δουλεύει και στην console του chrome αλλά καλύτερα στο site αυτό γιατί η console θυμάται τα const και θα σε αφήσει να κάνεις paste μόνο μια φορά ανά παράθυρο). Sample output πατώντας μερικές φορές το run: 1
defacer Δημοσ. 15 Δεκεμβρίου 2017 Δημοσ. 15 Δεκεμβρίου 2017 Πολύ σωστή η προσέγγιση του paparovic, ίσως λίγο overdone όσον αφορά το factory (ανάλογα και τι ακριβώς θες να κάνεις) αλλά η γενική ιδέα με την async function κλπ είναι ο πλέον μοντέρνος τρόπος να το κάνεις αυτό. Δύο ενστάσεις μόνο: Το if(result) που σχολιάστηκε κιόλας είναι βασικά bug γιατί αν κάποια promise γίνει resolve με falsy value θα τη χειριστεί σαν rejected. Τον βάφω μπλε, τον κόβω και τον πετάω στη θάλασσα ότι το arr.sort(() => Math.random() - 0.5) έχει πρόβλημα επειδή τυπικά η υλοποίηση του sort είναι quicksort και στην quicksort είναι απαραίτητο περισσότερο από άλλες μεθόδους η comparison function να είναι συνεπής. Ναι εδώ θέλουμε shuffle οπότε δε μας πειράζει αν δεν εμφανιστούν με τη "σωστή" random σειρά, αλλά πρώτον αυτό είναι λιγότερο random shuffle από όσο νομίζεις και δεύτερον μπορεί να οδηγήσει σε τρελλά σκαλώματα με μη-ντετερμινιστικό τρόπο. Το πρόβλημα είναι πως δε σου εγγυάται κανείς πόσες φορές και με ποιά ορίσματα θα κληθεί η comparison function, αλλά εσύ είσαι υποχρεωμένος αν στο compare(x, y) επιστρέψεις < 0 τότε στο compare (y, x) να επιστρέψεις > 0 -- πράγμα που προφανώς αυτή η comparison function δεν κάνει. 1
zynif Δημοσ. 15 Δεκεμβρίου 2017 Μέλος Δημοσ. 15 Δεκεμβρίου 2017 A τοσο καλή JavaScript δεν ξέρω. Και θα ήθελα μια πιο απλή προσέγγιση και με κλασεις. Ας πούμε τι λάθη εχει το παρακάτω. const axios = require('axios'); module.exports = class RetryPromise { constructor() { this.promiseChain = []; } promiseA() { var self = this; return new Promise(function (resolve, reject) { axios.get('endPointA').then((response) => { if (response.status === 200) { resolve({success: 1, promiseName:'A'}); } else { reject({success: -100}); } }).catch(error => { reject({success: -100}); }) }); } promiseB() { var self = this; return new Promise(function (resolve, reject) { axios.get('endPointB').then((response) => { if (response.status === 200) { resolve({success: 1, promiseName:'B'}); } else { reject({success: -100}); } }).catch(error => { reject({success: -100}); }) }); } addToChain(p) { this.promiseChain.push( function () { return new Promise(p); } ); } makePromiseChain() { this.addToChain(this.promiseA()); this.addToChain(this.promiseB()); } async runPromiseChain() { var self = this; let result; for (const p of self.promiseChain) { try { const invokedPromise = p(); result = await invokedPromise; if (result) { return result; } } catch (e) { } } } }; και φυσικά έχουμε και το αντίστοιχο unit test const assert = require('assert'); const sinon = require('sinon'); describe('PromiseChain: ', () => { let pChain = new RetryPromise(); it('All Fail', (done) => { const AlphaSpy = sinon.stub(pChain, 'promiseA').callsFake(() => { return new Promise(function (resolve, reject) { reject({success: -100}); }); }); const BetaSpy = sinon.stub(pChain, 'promiseB').callsFake(() => { return new Promise(function (resolve, reject) { reject({success: -100}); }); }); pChain.makePromiseChain(); pChain.runPromiseChain({}).then((data) => { done(); }, (error) => { assert.equal(AlphaSpy.calledAfter(BetaSpy),true); // SHOULD FE FALSE sinon.assert.calledOnce(AlphaSpy); sinon.assert.calledOnce(BetaSpy); assert.equal(-100, error.success); done(); }); }); }); PS : Στην ουσια το καθε promise υποτίθεται οτι κάνει ενα call σε ενα API και στο πρωτο API Που θα επιστρέψει success σταματα το chain
paparovic Δημοσ. 15 Δεκεμβρίου 2017 Δημοσ. 15 Δεκεμβρίου 2017 Το if(result) που σχολιάστηκε κιόλας είναι βασικά bug γιατί αν κάποια promise γίνει resolve με falsy value θα τη χειριστεί σαν rejected. True. Σε production code το ορθό θα ήταν `return await ...` Τον βάφω μπλε, τον κόβω και τον πετάω στη θάλασσα ότι το arr.sort(() => Math.random() - 0.5) έχει πρόβλημα επειδή τυπικά η υλοποίηση του sort είναι quicksort και στην quicksort είναι απαραίτητο περισσότερο από άλλες μεθόδους η comparison function να είναι συνεπής. Ναι εδώ θέλουμε shuffle οπότε δε μας πειράζει αν δεν εμφανιστούν με τη "σωστή" random σειρά, αλλά πρώτον αυτό είναι λιγότερο random shuffle από όσο νομίζεις και δεύτερον μπορεί να οδηγήσει σε τρελλά σκαλώματα με μη-ντετερμινιστικό τρόπο. Το πρόβλημα είναι πως δε σου εγγυάται κανείς πόσες φορές και με ποιά ορίσματα θα κληθεί η comparison function, αλλά εσύ είσαι υποχρεωμένος αν στο compare(x, y) επιστρέψεις < 0 τότε στο compare (y, x) να επιστρέψεις > 0 -- πράγμα που προφανώς αυτή η comparison function δεν κάνει. Ξανά true. To one-liner το πήρα από ένα Site: gist που είχε comment ένα link με την ανάλυση γιατί το συγκεκριμένο one-liner έχει τα προβλήματα που αναφέρεις. Και φυσικά το χρησιμοποίησα παρόλ' αυτά γιατί "one-liner that's not probabilistically fair" > "multi-liner that takes more visual space from the core educational code". Zynif: this.addToChain(this.promiseA()); Το `this.promiseA()` ξεκινάει το `axios.get` επιτόπου. Πρόσεξε καλύτερα τι έκανα add στο παράδειγμα μου (hint: `function(resolve, reject) {}`) Btw, αν είναι για δουλειά κι όχι για μάθηση: https://github.com/sindresorhus/p-lazy ή https://github.com/assaf/lazybird Προσωπικά χρησιμοποιώ διάφορα libs από τον sindresorhus και είμαι πολύ ευχαριστημένος από την ...δουλειά του
zynif Δημοσ. 15 Δεκεμβρίου 2017 Μέλος Δημοσ. 15 Δεκεμβρίου 2017 Χμμ έκανα τς εξης διορθώσεις addToChain(pm) { this.promiseChain.push(() => { return new Promise(pm); }); } makePromiseChain() { var self = this; this.addToChain( function(resolve, reject) { resolve(self.promiseA()) }); this.addToChain( function(resolve, reject) { resolve(self.promiseB()) }); } async runPromiseChain() { var self = this; for (const p of self.promiseChain) { try { const invokedPromise = p(); return await invokedPromise; } catch (e) { console.log(JSON.stringify(e)); } } } το θεμα ειναι οτι οταν το τρέχω ως const RetryPromise = require('./RetryPromise'); let pChain = new RetryPromise(); pChain.makePromiseChain(); pChain.runPromiseChain().then((data) => { console.log('DATA'); console.log(data); }).catch (e => { console.log('errors'); console.log(e); }); βγάζει data undefined ενώ θα πρέπει να εκτελειται το catch κομμάτι μιας και δεν υπάρχουν τα urls Που κάνω το http request
Προτεινόμενες αναρτήσεις
Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε
Πρέπει να είστε μέλος για να αφήσετε σχόλιο
Δημιουργία λογαριασμού
Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!
Δημιουργία νέου λογαριασμούΣύνδεση
Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.
Συνδεθείτε τώρα