Callbacks & Promises

9 min read
Callbacks & Promises

Αν ξεκινάς τώρα την JavaScript, τα callbacks και τα promises μπορεί να σου φαίνονται λίγο περίεργα στην αρχή. Ακόμα όμως και αν είσαι ποιο έμπειρος , παρακαλώ μείνε μέχρι τέλος γιατι θα ξανα φρεσκάρεις τις γνώσεις σου.

Τι είναι το callback ;

Callback είναι μια συνάρτηση που τη δίνεις σαν όρισμα σε άλλη συνάρτηση, για να εκτελεστεί αργότερα ή όταν συμβεί κάτι, πχ ένα σφάλμα η κάποιο άλλο event.

ας δούμε ενα απλό παράδειγμα.

function sayHello(name) {
	console.log('Γεια σου ' + name)
}

function processUser(callback) {
	const username = 'Nikos'
	callback(username)
}

processUser(sayHello)

Τι γίνεται εδώ;

  • Έχουμε τη sayHello
  • Έχουμε τη processUser
  • Η processUser παίρνει μια άλλη συνάρτηση σαν όρισμα

Καλούμε την proccessUser η οποία με την σειρά της καλεί την sayHello
με όρισμα το name. Η callback εδω είναι η sayHello


Callback με arrow function

Πολύ συχνά δεν γράφουμε ξεχωριστή συνάρτηση, αλλά δίνουμε κατευθείαν μια ανώνυμη συνάρτηση.

function processUser(callback) {
	const username = 'Alexandros'
	callback(username)
}

processUser((name) => {
	console.log('Καλώς ήρθες ' + name)
})

Γιατί υπάρχουν τα callbacks;

Τα callbacks υπάρχουν γιατί πολλές φορές θέλουμε μια συνάρτηση να είναι πιο ευέλικτη. Αντί να αποφασίζει μόνη της τι θα κάνει, της δίνουμε εμείς τη “συνέχεια”.

function calculate(a, b, action) {
	const result = action(a, b)
	console.log(result)
}

calculate(5, 3, (x, y) => x + y) // 8
calculate(5, 3, (x, y) => x * y) // 15
calculate(5, 3, (x, y) => x - y) // 2

Η calculate δεν ξέρει από μόνη της τι πράξη θα κάνει. Tην ορίζουμε με callback.


Sync / Async callbacks

Τα callbacks χωρίζονται χοντρικά σε 2 κατηγορίες:

  1. sync callbacks
  2. async callbacks

1. Sync callback

Το sync callback τρέχει εκείνη τη στιγμή, μέσα στη ροή του προγράμματος.

function greet(name, formatter) {
	const formattedName = formatter(name)
	console.log('Γεια σου ' + formattedName)
}

greet('nikos', (n) => n.toUpperCase())

εμφανίζει το μήνυμα, Γεια σου NIKOS αμέσως.

2. Async callback

Το async callback εκτελείται αργότερα. Το πιο κλασικό παράδειγμα είναι το setTimeout το οποιο ειναι function.

console.log('Εκτελούμαι σε λίγο...')

setTimeout(() => {
	console.log('Εκτελέστηκα μετά από 2 δευτερόλεπτα')
}, 2000)

console.log('Τελείωσα')

Η ροή του προγράμματος

  1. Εκτελούμαι σε λίγο…
  2. Τελείωσα
  3. Εκτελέστηκα μετά από 2 δευτερόλεπτα

Άρα το callback μέσα στο setTimeout δεν εκτελείται αμέσως. Εκτελείται αργότερα, δλδ μετά απο 2 δευτερόλεπτα.


Ένα πιο κατανοητό παράδειγμα

Σε αυτό το παράδειγμα ας πούμε οτι φτιάχνουμε εναν καφέ.

function makeCoffee(callback) {
	console.log('Ο καφές ετοιμάζεται...')

	setTimeout(() => {
		console.log('Ο καφές είναι έτοιμος!')
		callback()
	}, 2000)
}

function drinkCoffee() {
	console.log('Τέλεια, τώρα μπορώ να ξυπνήσω')
}

makeCoffee(drinkCoffee)

Η Ροή της εκτέλεσης:

  1. Ξεκινάει η παραγγελία.
  2. Περιμένουμε 2 δευτερόλεπτα.
  3. Μόλις “ετοιμαστεί” ο καφές, εκτελείται το callback.

Παράδειγμα με addEventListener

const button = document.querySelector('button')

button.addEventListener('click', () => {
	console.log('Πάτησες το κουμπί')
})

Το callback εδώ θα τρέξει όταν ο χρήστης κάνει click.


Παράδειγμα με forEach

const numbers = [1, 2, 3]

numbers.forEach((num) => {
	console.log(num)
})

Η συνάρτηση που δίνεις στο forEach είναι callback.


To πρόβλημα Callback Hell

Τα callbacks, όπως καταλάβατε, είναι αρκετά χρήσιμα για τη λειτουργία ενός προγράμματος. Το πράγμα γίνεται δυσκολότερο όταν έχουμε πολλαπλά callbacks το ένα μέσα στο άλλο. Το πρόβλημα ονομάζεται 👹 callback hell, και όχι άδικα.

Ας δούμε ενα πάραδειγμα:

step1((result1) => {
	step2(result1, (result2) => {
		step3(result2, (result3) => {
			step4(result3, (result4) => {
				console.log(result4)
			})
		})
	})
})

Προβλήματα αυτής τις προσσέγισης:

  • Ο κώδικας πηγαίνει όλο και πιο μέσα.
  • Ο κώδικας διαβάζεται πολύ δύσκολα.
  • Δύσκολη διαχείριση & επίλυση σφαλμάτων.

Παράδειγμα callback hell με errors

doA((err, a) => {
	if (err) {
		console.log('Σφάλμα στο A:', err)
		return
	}

	doB(a, (err, b) => {
		if (err) {
			console.log('Σφάλμα στο B:', err)
			return
		}

		doC(b, (err, c) => {
			if (err) {
				console.log('Σφάλμα στο C:', err)
				return
			}

			console.log('Τελικό αποτέλεσμα:', c)
		})
	})
})

Δεν είναι αδύνατο να το καταλάβεις, αλλά όσο μεγαλώνει τόσο πιο κουραστικό γίνεται.


Eδώ έρχονται τα Promises

Τα promises ήρθαν για να κάνουν τον async κώδικα πιο οργανωμένο και πιο εύκολο στο διάβασμα.Τόσο για τον συγγραφέα του κώδικα όσο και τους αναγνώστες του.

Ένα Promise είναι ένα αντικείμενο που αντιπροσωπεύει το αποτέλεσμα μιας ασύγχρονης λειτουργίας.Θα σου δώσω το αποτέλεσμα αργότερα. Ή θα πετύχω, ή θα αποτύχω.

Oι Καταστάσεις ενός Promise η αλλιώς (PFR)

  1. Pending
    Περιμένει ακόμα

  2. Fulfilled
    Ολοκληρώθηκε επιτυχώς

  3. Rejected
    Απέτυχε

Μόλις γίνει fulfilled ή rejected, δεν αλλάζει ξανά.


Πώς φτιάχνουμε ένα Promise;

const makeCoffee = new Promise((resolve, reject) => {
	const success = true

	if (success) {
		resolve('Ο καφές είναι έτοιμος.')
	} else {
		reject('Κάτι πήγε λάθος στην παρασκευή του καφέ.')
	}
})

Παράδειγμα Promise με καθυστέρηση

const coffeePromise = new Promise((resolve, reject) => {
	console.log(' Ο καφές φτιάχνεται...')

	setTimeout(() => {
		const coffeeReady = true

		if (coffeeReady) {
			resolve('Ο καφές είναι έτοιμος')
		} else {
			reject('Η μηχανή καφέ χάλασε')
		}
	}, 2000)
})

Εδώ το promise περιμένει 2 δευτερόλεπτα και μετά:

  • είτε κάνει resolve
  • είτε κάνει reject

Εδω οι λόγοι που μπορεί να πάνε λάθος τα πράγματα είναι πάρα πολλοί. Για παράδειγμα, μπορει να χαλάσει η μηχανή μας, να μην έχουμε κάποιο βασικό συστατικό για την παρασκευή του καφέ οπότε όλη η διαδικασία αποτυγχάνει.


Πώς χρησιμοποιώ ένα Promise;

Εχουμε πρόσβαση σε ενα promise με τις εξής μεθόδους:

  • .then()
  • .catch()
  • .finally()

.then()

Το .then() εκτελείται όταν το promise πετύχει.

coffeePromise.then((message) => {
	console.log(message)
})

.catch()

Το .catch() εκτελείται όταν το promise αποτύχει.

coffeePromise.catch((error) => {
	console.log(error)
})

.finally()

Το .finally() εκτελείται πάντα, είτε πετύχει είτε αποτύχει.

coffeePromise.finally(() => {
	console.log('Η διαδικασία τελείωσε')
})

Πλήρες παράδειγμα με Promise


const coffeePromise = new Promise((resolve, reject) => {
	setTimeout(() => {
		const coffeeReady = true

		if (coffeeReady) {
			resolve('Ο καφές είναι έτοιμος')
		} else {
			reject('Δεν έγινε ο καφές')
		}
	}, 2000)
})

coffeePromise
	.then((message) => {
		console.log('Success:', message)
	})
	.catch((error) => {
		console.log('Error:', error)
	})
	.finally(() => {
		console.log('Τέλος διαδικασίας')
	})
    ```

Promise chaining problem

Όταν βάζουμε πολλά .then() το ένα μετά το άλλο, αυτό λέγεται chaining.

getUser()
	.then((user) => getPosts(user.id))
	.then((posts) => getComments(posts[0].id))
	.then((comments) => {
		console.log(comments)
	})
	.catch((err) => {
		console.log(err)
	})

Then() χωρίς return

Ένα απο τα βασικότερα λάθη που κάνω ακόμα και σήμερα είναι να μην βάζω return της callback function μέσα στην then.

Σωστή προσσέγιση


getUser()
	.then((user) => {
		return getPosts(user.id)
	})
	.then((posts) => {
		console.log(posts)
	})
    ```

Σωστή προσσέγιση


getUser()
	.then((user) => getPosts(user.id))
	.then((posts) => {
		console.log(posts)
	})```

Λάθος προσσέγιση χωρίς return

getUser()
	.then((user) => {
		getPosts(user.id)
	})
	.then((posts) => {
		console.log(posts)
	})

Γιατί είναι λάθος;

Γιατί δεν κάναμε return το επόμενο promise, άρα το επόμενο .then() δεν θα πάρει το αποτέλεσμα που περιμένουμε.


Ολοκληρωμένο παράδειγμα Promise

function fetchUser() {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			const user = { id: 1, name: 'Nikos' }
			resolve(user)
		}, 1000)
	})
}

fetchUser()
	.then((user) => {
		console.log('Βρέθηκε χρήστης:', user)
	})
	.catch((err) => {
		console.log('Κάτι πήγε λάθος:', err)
	})

Reject με Error object

Καλύτερα στα errors να χρησιμοποιούμε Error.

const p = new Promise((resolve, reject) => {
	const ok = false

	if (ok) {
		resolve('Επιτυχία')
	} else {
		reject(new Error('Κάτι πήγε στραβά'))
	}
})

p.catch((err) => {
	console.log(err.message)
})

Έτσι έχουμε πιο σωστή διαχείριση σφαλμάτων.


Promises και async λειτουργίες

Τα promises χρησιμοποιούνται παντού σε async πράγματα, όπως:

  • requests σε API
  • διάβασμα αρχείων
  • βάσεις δεδομένων
  • timers
  • network calls

Παράδειγμα με fetch:


fetch('https://api.github.com/users')
	.then((response) => response.json())
	.then((data) => {
		console.log(data)
	})
	.catch((err) => {
		console.log('Σφάλμα:', err)
	})

    ```

Τι κάνει το response.json();

  • Το fetch() επιστρέφει promise.
  • Το response.json() επίσης επιστρέφει promise.

fetch(url)
	.then((response) => response.json())
	.then((data) => {
		console.log(data)
	})

    ```

Συνδυασμός πολλαπλών Promises

Το Promise.all() χρησιμοποιείται όταν θέλεις να τρέξουμε πολλά promises μαζί και να περιμένουμε να ολοκληρωθούν όλα.

const p1 = Promise.resolve('A')
const p2 = Promise.resolve('B')
const p3 = Promise.resolve('C')

Promise.all([p1, p2, p3]).then((results) => {
	console.log(results)
})

To αποτέλεσμα είναι:

;['A', 'B', 'C']

Aποτυχία ενός promise

Στο Promise.all, αν ένα promise αποτύχει, αποτυγχάνει όλο.


const p1 = Promise.resolve('OK')
const p2 = Promise.reject(new Error('Έσκασε'))
const p3 = Promise.resolve('OK 2')

Promise.all([p1, p2, p3])
	.then((results) => {
		console.log(results)
	})
	.catch((err) => {
		console.log('Σφάλμα:', err.message)
	})
    ```

Promise.allSettled

Eίναι χρήσιμο όταν θέλουμε να δούμε τι έγινε με όλα, χωρίς να σταματήσουμε στο πρώτο error.

const p1 = Promise.resolve('Πρώτο')
const p2 = Promise.reject('Λάθος')
const p3 = Promise.resolve('Τρίτο')

Promise.allSettled([p1, p2, p3]).then((results) => {
	console.log(results)
})

Αποτέλεσμα:

;[
	{ status: 'fulfilled', value: 'Πρώτο' },
	{ status: 'rejected', reason: 'Λάθος' },
	{ status: 'fulfilled', value: 'Τρίτο' },
]

Promise.race

Eπιστρέφει όποιο promise τελειώσει πρώτο.

const p1 = new Promise((resolve) => {
	setTimeout(() => resolve('Πρώτο τελείωσε'), 1000)
})

const p2 = new Promise((resolve) => {
	setTimeout(() => resolve('Δεύτερο τελείωσε'), 2000)
})

Promise.race([p1, p2]).then((result) => {
	console.log(result)
})

Promise.any

Eπιστρέφει το πρώτο promise που θα πετύχει.

const p1 = Promise.reject('Λάθος 1')
const p2 = new Promise((resolve) => {
	setTimeout(() => resolve('Επιτυχία!'), 1000)
})
const p3 = Promise.reject('Λάθος 2')

Promise.any([p1, p2, p3]).then((result) => {
	console.log(result)
})

Callbacks vs Promises

Callbacks

ΥΠΕΡ 👍

  • είναι απλά σαν ιδέα.
  • υπάρχουν παντού στη JavaScript.
  • πολύ χρήσιμα σε events και array methods.

ΚΑΤΑ 👎

  • σε πολλά async βήματα γίνονται μπελάς.
  • οδηγούν εύκολα σε callback hell.
  • η διαχείριση σφαλμάτων είναι πιο άβολη

Promises

ΥΠΕΡ 👍

  • πιο καθαρός async κώδικας.
  • πολύ καλύτερο chaining.
  • πιο εύκολη διαχείριση errors.
  • συνεργάζονται τέλεια με async/await

ΚΑΤΑ 👎

  • δύσκολα στην κατανόηση στην αρχή.

Επομενη στάση Async / Await

Το async/await είναι ουσιαστικά ένας πιο καθαρός τρόπος να δουλεύουμε με promises. Πρόσεξε, (δεν τα αντικαθιστα) απλά πατάει πάνω σε αυτά, και μας βοηθάνε να τα διαχειριστούμε ποιο εύκολα.

Async Function

Όταν βάζεις async πριν από μια συνάρτηση, αυτή η συνάρτηση επιστρέφει promise.

async function test() {
	return 'Γεια'
}

test().then((value) => {
	console.log(value)
})

Παρόλο που φαίνεται ότι επιστρέφει απλό string, στην πραγματικότητα επιστρέφει promise 😂. Ξέρω είναι λίγο δυσνόητο, αλλα θα το καταλάβεις αργότερα.

Αwait

Το await περιμένει να ολοκληρωθεί ένα promise.

function getUser() {
	return new Promise((resolve) => {
		setTimeout(() => {
			resolve({ id: 1, name: 'Maria' })
		}, 1000)
	})
}

async function showUser() {
	const user = await getUser()
	console.log(user)
}

showUser()

Try/Catch με async-await

Αν ένα promise αποτύχει, το πιάνουμε με try/catch.

function login() {
	return new Promise((resolve, reject) => {
		const success = false

		setTimeout(() => {
			if (success) {
				resolve('Μπήκες επιτυχώς')
			} else {
				reject(new Error('Λάθος στοιχεία'))
			}
		}, 1000)
	})
}

async function startLogin() {
	try {
		const message = await login()
		console.log(message)
	} catch (err) {
		console.log('Σφάλμα:', err.message)
	}
}

startLogin()

Πότε callbacks και πότε promises;

Όπως λέει και το τραγούδι, «πότε Βούδας, πότε Κούδας», αλλά ας δούμε σε ποιες περιπτώσεις χρειαζόμαστε την κάθε προσέγγιση.

Πότε χρειάζομαι callback

  • όταν έχουμε event & error handlers.
  • δουλεύουμε με array μεθόδους (forEach, map, filter).
  • έχουμε απλή λειτουργία που χρειάζεται “συνέχεια”.

Πότε χρειάζομαι promise

  • όταν έχουμε async λειτουργίες.
  • θέλουμε καλύτερη ροή στην εφαρμογή μας.
  • θέλουμε chaining & καθαρό και ευανάγνωστο κώδικα.

Συχνά λάθη στα callbacks

1. Καλούμε το callback αντί να το περάσουμε

Λάθος

setTimeout(sayHello(), 1000)

Σωστό

setTimeout(sayHello(), 1000)
  • Στο λάθος παράδειγμα, η sayHello() εκτελείται αμέσως.
  • Ενώ το setTimeout θέλει να του δώσουμε τη συνάρτηση για αργότερα.

2. Δεν ελέγχουμε αν το callback υπάρχει

Eδώ αν δεν δοθεί callback, θα σκάσει 💣.

function runTask(callback) {
	callback()
}

Καλύτερη προσσέγιση:


function runTask(callback) {
	if (typeof callback === 'function') {
		callback()
	}
}```

Συχνά λάθη στα promises

1. Βάζουμε await σε sync functions

Πρόσεξε, το await δουλεύει μόνο μέσα σε async function.

2. Ξεχνάμε το return

doA()
	.then((a) => {
		doB(a)
	})
	.then((b) => {
		console.log(b)
	})```

Το b εδώ δεν θα είναι αυτό που περιμένουμε.

3. Ξεχνάμε το catch

fetchData().then((data) => {
	console.log(data)
})

Αν αποτύχει, δεν έχουμε πιάσει το error.

Καλύτερα να το κάνουμε έτσι:


fetchData()
	.then((data) => {
		console.log(data)
	})
	.catch((err) => {
		console.log(err)
	})```

Τελικό συμπέρασμα

Τα callbacks είναι η βάση, τα promises ήρθαν για να οργανώσουν καλύτερα τον async κώδικα,και το async/await ήρθε για να κάνει τη ζωή μας ακόμα πιο εύκολη. Μην φοβάστε να πειραματιστείτε και να «λερώσετε» τα χέρια σας. Όλα αυτά είναι εργαλεία· όσο περισσότερο εξασκείστε, τόσο πιο ευέλικτοι γίνεστε.

Βρήκες κάποιο σφάλμα ?

Επειδή δεν είμαι guru της JavaScript μπορεί κάπου να έχω κάνει κάποιο λάθος, η να έχω δώσει λάθος παράδειγμα. Αν εντοπίσεις κάτι τέτοιο, θα εκτιμούσα να μου στείλεις ένα email ώστε να το διορθώσω.

Suggest an edit

Last modified: 17 Mar 2026