Utvecklingstips

Asynkron programmering del 2

En av de stora anledningarna till att Javascript har fått en nyrenässans de senaste åren är på grund av dess möjlighet att skicka funktioner som objekt. Detta möjliggör ett väldigt smidigt sätt att skriva asynkron kod. Istället för att vänta på att resultatet från ett databasanrop ska bli klart kan man skapa en anonym funktion som tar hand om resultatet och under tiden kan resten av koden köras klart.

Tänk på följande kodsnuttar:

// synkron
var getUserFromDatabase(userId) { 
	return User.FindOneSync(userId);
}
// asynkron
var getUserFromDatabase(userId, done) { 
	User.FindOne(userId, function(err, user) {
		done(err, user);
	});
}

I det asynkrona exemplet kommer resultatet från databasen inte hindra resterande kod från att exekveras direkt, utan istället fortsätta tills att alla anrop är klara. Anropen till ovanstående funktioner blir olika:

// synkront anrop
var user = getUserFromDatabase(id); 
console.log(user);
getUserFromDatabase(id, function(err, user) {  
	console.log(user);
});

Några problem kan uppstå när man använder detta mönster. Fundera på nedanstående kod:

function getAndUpdateOrCreateUser(id, done) { 
	getUserFromDatabase(id, function(err, user) {
		if (user) {
			updateUser(user, function(err, user) {
				done(err, user);
			});    
		}      
		user = new User();    
		done(null, user);
	});
}
getAndUpdateOrCreateUser(1337, function(err, user) {  
	console.log(user); // is always called twice. First one with an empty user, secondly with the existing or newly created user.
});

Den logikbugg som finns inbyggd i ovanstående mönster är att man inte tänker på att koden kommer köras vidare medan det asynkrona anropet till updateUser körs. Dvs koden går vidare till new User() och därefter körs callbackfunktionen för den nya användaren. Eftersom funktionen fortfarande är aktiv kommer även den dessutom köras en gång till för den befintliga användaren och du får alltså två anrop tillbaka från din funktion.

Den korrekta koden skulle varit:

function getAndUpdateOrCreateUser(id, done) {  
	getUserFromDatabase(id, function(err, user) {     
		if (user) {      
			updateUser(user, function(err, user) {        
				done(err, user);      
			});    
		} else {        
			user = new User();      
			done(null, user);        
		}  
	});
}
getAndUpdateOrCreateUser(1337, function(err, user) {  
	console.log(user); // is only run once
});

Om man börjar skriva sin kod på ovanstående sätt blir koden snabbt väldigt svårläslig för allt måste hamna i if/else rader. Ett annat och bättre sätt att förhindra att resten av koden körs vidare är att lägga till return framför alla asynkrona anrop vilket gör att den ursprungliga funktionen avslutas och inget körs vidare. Det asynkrona anropet kommer fortfarande peka rätt på grund av något som kallas för closures. Closures är en mekanism som analyserar vilka variabler du använder i din privata kod och behåller en referens till dem under tiden som ditt anrop är aktivt. Dessutom frigör return det minne som temporärt är reserverat till din funktion som annars ligger och väntar på att alla referenser är borttagna och garbage collectorn går in och rensar dem.Tänk bara på att return endast returnerar ett steg i taget så om du har en funktion som kör en funktion behöver du köra return på båda för att hela anropet ska avslutas.

function getAndUpdateOrCreateUser(id, done) {  
	getUserFromDatabase(id, function(err, user) {     
		if (user) {      
			return updateUser(user, function(err, user) {        
				return done(err, user);       
			});    
		}
		user = new User();    
		return done(null, user);    
	});
}
getAndUpdateOrCreateUser(1337, function(err, user) {  
	console.log(user); // is only run once
});

Avslutningsvis så kan man säga att det oftast är enklast att programmera synkront men prestandaskillnad du får vid en asynkron bearbetning av ett flöde är enormt stor. I node.js är det dessutom det enda sättet att skriva sin kod eftersom node.js är entrådat. Läs gärna min andra blog om asynkron programmering.

Andra bloggar om: 
 
Christian Landgren

2012-09-03 kl. 15:36

Web Analytics