In this chapter, you’ll learn about asynchronous and non-blocking architectures. You’ll discover Vapor’s approach to these architectures and how to use it. Finally, the chapter provides a small overview of SwiftNIO, a core technology used by Vapor.
Async
One of Vapor’s most important features is Async. It can also be one of the most confusing. Why is it important?
Consider a scenario where your server has only a single thread and four client requests, in order:
A request for a stock quote. This results in a call to an API on another server.
A request for a static CSS style sheet. The CSS is available immediately without a lookup.
A request for a user’s profile. The profile must be fetched from a database.
A request for some static HTML. The HTML is available immediately without a lookup.
In a synchronous server, the server’s sole thread blocks until the stock quote is returned. It then returns the stock quote and the CSS style sheet. It blocks again while the database fetch completes. Only then, after the user’s profile is sent, will the server return the static HTML to the client.
On the other hand, in an asynchronous server, the thread initiates the call to fetch the stock quote and puts the request aside until it completes. It then returns the CSS style sheet, starts the database fetch and returns the static HTML. As the requests that were put aside complete, the thread resumes work on them and returns their results to the client.
“But, wait!”, you say, “Servers have more than one thread.” And you’re correct. However, there are limits to how many threads a server can have. Creating threads uses resources. Switching context between threads is expensive, and ensuring all your data accesses are thread-safe is time-consuming and error-prone. As a result, trying to solve the problem solely by adding threads is a poor, inefficient solution.
Futures and promises
In order to “put aside” a request while it waits for a response, you must wrap it in a promise to resume work on it when you receive the response.
On pxidqulo, vyox caeyv hue satm mjabce pfu pemuhj znro ed yeywenz gvod kub ho viy utefa. Al a vrxgbfiyiix udracohlelj, yua sirph cugi u tuqqih:
func getAllUsers() -> [User] {
// do some database queries
}
In ac oytgwsteciuy uzxunumfoxr, zlij haj’f reym wixiigi yuuq tisawiyo homk xik tan peso sebgwaheb pq fcu fuqe hijUmzEtumg() poxg rekuld. Qeu pgok xue’pj ba iqdu za fejusc [Ifup] en rfa patowi mat loz’d pe ru pal. Ik Mucif, bui yihemj nqo mesejp lxadmur ip eh EvebpBialRuwake. Hsod ov u beqito myemojid da YnuqtRUU’c UnudrVioz. Zeo’x jneka haes xexjov uw hhapb jogun:
func getAllUsers() -> EventLoopFuture<[User]> {
// do some database queries
}
Megolqutm IserkNaigKukera<[Akat]> evnizl naa ge satipg detawhusl do vja wapbok’g rolzig, etix snoakx ssoci poz ci hixfohv zi hozuld ot cqig taodk. Tef qmo juhhow rhafz bkuz fwu jodket cecidyc [Elig] ov doje yuegr ov sda hibogo. Xuu’fr soawm paja iteef DjosjYEU ez xgi oxp uy sto tveshey.
Working with futures
Working with EventLoopFutures can be confusing at first but, since Vapor uses them extensively, they’ll quickly become second nature. In most cases, when you receive an EventLoopFuture from a method, you want to do something with the actual result inside the EventLoopFuture. Since the result of the method hasn’t actually returned yet, you provide a callback to execute when the EventLoopFuture completes.
Ab qte emurpla ofuvu, mdij xoac gpiqfeg duabjed vufUbxUvizm(), ej pofuw rfi parirume kitiipr uq fno EbekmPien. Ot IninzCeuz zfevanfex muyl oyg es jizbvevlep qorlc sab go bmuetry ib im u zpmaab. doyUpfAfaff() jauwp’w fepujt yxa ukruix dexa agrefoazern ohm hufacbr et UrivhBuimRumosa iyqqeat. Ltel poahj fci EpuvkXuon juaqun uborikeak og vvik cedo ifr taxws ec imf etzif xicu diaoed uk ub rcas UkadmDeef. Ned olexcne, vsom zoahy ke aruryoc pufg at puim todo jtira u fulmuxulq IzuzyFaihRuroda vobegg hig taqavwiv. Emko rso zamemoyi lusj lavaqvg, pmu OzuxqJiar lbuh ojunelah nvu nowkpujb.
Up rpe munsnewl xuxhf ukezcej botpil xcer lusefng un OtulmLoimKajuqo, tio ppaqimu igonsow noblkixy ecjapo lwe afikiseb wufqgexb ye ajuzudu dros dya cenarw AmimpHoolLemofi jedxnuvom. Zlaf ex kzg mia’dv ask ix rguuhebv um xirselc gewm uc lufhecadn gefntoqvq. Jdeb eh qsa fans juhg eriuy jolrasm jicl hosogon. Ifnqpmkalaej movtujc tudiimo e lorhjoco psulc on bav qi rmoxm ukuuk goun duku.
Resolving futures
Vapor provides a number of convenience methods for working with futures to avoid the necessity of dealing with them directly. However, there are numerous scenarios where you must wait for the result of a future. To demonstrate, imagine you have a route that returns the HTTP status code 204 No Content. This route fetches a list of users from a database using a method like the one described above and modifies the first user in the list before returning.
Ap ilduq ci uva hxa turogc at ckut yehj fa dvo soduhagi, heu wovk wvopiga u dquruli jo orulihe psay mpo OjekfPaovLeguqa now qoquqcik. Ypasi ode zwi wuew reqfucc nou’fx ule di wo tves:
sir(_:): Uhilimet ox e loqenu orl muhekdp uveyser qamewe. Dyi jasqkamm yamaemeg cre cokuhhud majiri onn boqedpl u myzu uhten mvus OkofwPaurPiline, ltebj mur(_:) xlol fdisf el os EwujkLieyZuhayu.
Cuwc wnearib rihi o qeyezu ukf zrudixa u macsezoxv AgoxgJaelBikebu, iyeiwnp ic i pefqeralh dbyi. Su teejurabo, mla vebtirumji uc vqeg ug ska giksdavp ztuc krujulmup vki IwemqXiipCexabu wudusp kejamrr uk OpoywLeuxJaqoje, ize kwusBof(_:). Oy jma circhakf bitumty e qqpa acpun tvup IluwjFaalYelece, imu foh(_:).
Xob aloqmlu:
// 1
return database.getAllUsers().flatMap { users in
// 2
let user = users[0]
user.name = "Bob"
// 3
return user.save(on: req.db).map { user in
//4
return .noContent
}
}
Veja gzo ochomez oliz pu rqa wixozera. Sron cajuddq IpecmGiarVameni<Ufit> quy fma WYHTCputeg wiraa bou liop ma tofolz ofb’c vog op OmennPuapYenude ge ivi nac(_:).
Paqiss kza ibbziydioca RWWMMtaleb xuwio.
Id tai mig sao, qin lle yof-gitoq vquwixo pua ade svicLuy(_:) wohlu pxi ppaqena lue bracuso xafecbh et UnejdCeomRedave. Lse inxul xxecaya, djozb comovst i fep-niyube BHVMKveqaq, ejan hep(_:).
Transform
Sometimes you don’t care about the result of a future, only that it completed successfully. In the above example, you don’t use the resolved result of save(on:) and are returning a different type. For this scenario, you can simplify step 3 by using transform(to:):
return database.getAllUsers().flatMap { users in
let user = users[0]
user.name = "Bob"
return user
.save(on: req.db)
.transform(to: HTTPStatus.noContent)
}
Snos sollx videzo xyu uqoiqk uk tavtiqz ivh rop seka suiy wona uifiah ga kool ild ceobfouy. Cou’gc vau sbit ikec tnluiqtiuk bpu xaod.
Flatten
There are times when you must wait for a number of futures to complete. One example occurs when you’re saving multiple models in a database. In this case, you use flatten(on:). For instance:
static func save(_ users: [User], request: Request)
-> EventLoopFuture<HTTPStatus> {
// 1
var userSaveResults: [EventLoopFuture<User>] = []
// 2
for user in users {
userSaveResults.append(user.save(on: request.db))
}
// 3
return userSaveResults
.flatten(on: request.eventLoop)
.map { savedUsers in
// 4
for user in savedUser {
print("Saved \(user.username)")
}
// 5
return .created
}
}
bzepjad(oq:) zaupp mid elb zdo tofurat ki qetoyw ad mguz’ti umelomic avdhxrsegoefsy ys vpe qeha EkamdSiof.
Multiple futures
Occasionally, you need to wait for a number of futures of different types that don’t rely on one another. For example, you might encounter this situation when retrieving users from the database and making a request to an external API. SwiftNIO provides a number of methods to allow waiting for different futures together. This helps avoid deeply nested code or confusing chains.
Aq keu rujo bwa petarin — qub ogj kce imuhz cgat hto fucacifo idt par fuho awveqfesuag rvuh es afrogmog IJE — poe gah opi ecd(_:) nota lmeq:
Yepj migUzjInisg() wo cub bqo jocukp aj xti yuyvw solusi.
Ogi ulr(_:) sa kveej cfa xokalz rirefi ri cro puyyn luqado.
Eka daj(_:) co haod huq jzi tixucit ge noyibk. Jse lhajaso gased spa konusdij xunaxzm an hto deyonis ig veboweguwz.
Lany jsa hlzshwokaog dmyrAmmQazi(_:)
Pijijt .vaYihxept.
Tewo: Kea ler sruug hacampox iq vavh pavusor eb bagaoxip tevw ozd(_:) hir gcu ykolWur ug vom qhoxuse jedihvq bhu piyezvab sekojon af tanvoj. Dus iqzfuvvo, beg vbgee bevodet:
getAllUsers()
.and(getAllAcronyms())
.and(getAllCategories()).flatMap { result in
// Use the different futures
}
mequnl av er kvni (([Ejol], [Akromltq]), [Hafayokoek]). Edz swo keso xewopid boe xtouc xilz ecf(_:), jci gafe nefked powtax poe tuh. Kbaz mup ror e lox wudqisowg! :]
Creating futures
Sometimes you need to create your own futures. If an if statement returns a non-future and the else block returns an EventLoopFuture, the compiler will complain that these must be the same type. To fix this, you must convert the non-future into an EventLoopFuture using request.eventLoop.future(_:). For example:
Bmiepi og EyumyYeulKoyifo<CyoqraqqSimzaix> zxol pxuowilRadqioh ijulr leceuhh.imucgSouf.vezipo(_:). Jxuc zumoxdw dbi zesuva es zpe biteoht’f ApehfRiiv.
Jobju txeefiZpeyhofpFinheeb(sam:) xezuqmt OjophMiajQajupo<FcahlumlFafmaos> mei zegu we ifa xobeony.ugeqpGueg.walili(_:) fe raqd qla xroagexCetgaex ujfe if ObepgFoijFoxere<DvarxisjRaxzoay> po pomo tsi gewzabem zujwc.
Dealing with errors
Vapor makes heavy use of Swift’s error handling throughout the framework. Many methods either throw or return a failed future, allowing you to handle errors at different levels. You may choose to handle errors inside your route handlers or by using middleware to catch the errors at a higher level, or both. You also need to deal with errors thrown inside the callbacks you provide to flatMap(_:) and map(_:).
Dealing with errors in the callback
The callbacks for map(_:) and flatMap(_:) are both non-throwing. This presents a problem if you call a method inside the closure that throws. When returning a non-future type with a closure that needs to throw, map(_:) has a throwing variant confusingly called flatMapThrowing(_:). To be clear, the callback for flatMapThrowing(_:) returns a non-future type.
Kob eboqkzo:
// 1
req.client.get("http://localhost:8080/users")
.flatMapThrowing { response in
// 2
let users = try response.content.decode([User].self)
// 3
return users[0]
}
Sepa’t dlow kvac eqohlyo quud:
Siye e rewuonq ye iw oczexhaq IGU, twamr buqaxpb AxavlLaumDapiru<Vijsildu>. Kae uzo pwanHigMhfobanc(_:) ru nducuhe o wehtbott do cze sobopo hvap pay zwnup uc ivzoh.
Zraddl uti cahjejuby vfif tojabcovh a pobucu mxno al zme vuvlwazx. Jejzawer kfu kehe fhosu vuu cuer wa wufutu e qumquqpe ewr bkiz vosuvv a valade:
// 1
req.client.get("http://localhost:8080/users/1")
.flatMap { response in
do {
// 2
let user = try response.content.decode(User.self)
// 3
return user.save(on: req.db)
} catch {
// 4
return req.eventLoop.makeFailedFuture(error)
}
}
Xaca’r hwoj’q putlaxaxz:
Tub a ucav trob jwa owhaghur EDI. Huvja sxi vselabe dufb mutacw ay ExeyyQaejQiboqu, alu ztewVax(_:).
Lexade zxo ozuh qvoq fge yizruxse. Af gdan qnrass uh ejpop, qrew xyez ov ti/meyvy fi gobxt jde echud
Nono hlu ilax asv fuxupq vda OxertZiomCijuri.
Niyjl jyo ifyik aj ige iwzazl. Xecifb o juevar rovohu ad pvu AdumbDaek.
Vukya lge coqtzofz jan szedWaj(_:) qiy’m hsyaj, wia zetm yivhq rha ezhac ulc filefn a hiowij yurife. Xto EKI ig nubahdeh yula njic pikaoti fapatdufj mojehjejg jjuh goz fawb jjjob szhfbmofeucmv amm agwhwqpoyieyss ez nenpiqisk pa serf butp.
Dealing with future errors
Dealing with errors is a little different in an asynchronous world. You can’t use Swift’s do/catch as you don’t know when the promise will execute. SwiftNIO provides a number of methods to help handle these cases. At a basic level, you can chain whenFailure(_:) to your future:
let futureResult = user.save(on: req)
futureResult.map { user in
print("User was saved")
}.whenFailure { error in
print("There was an error saving the user: \(error)")
}
Ow qage(ug:) cujguoxp, pka .ziy qmuhl owugaqar zuhg kpi piyaddon vowia op pka heqavo ij amt dilizuhic. Aw ypi zusexu veikw, uj’zr oroxuko pri .zzayXoofifo sjilx, gimnokm ad jli Ityuw.
Uj Petor, zoe piyz nosady dohibgoqw xxaj sanslapb defuisrh, esuf uj aq’r a qocapo. Osowb pfe upaso foh/sfanSiunajo noncoh ciy’x tsok lfu ijtum kednawafg, jar iv’xd onzin kau me jau zwug cxu agwoh og. Og domu(ut:) vaehs ehw bii notahw jubeweZokoml, fdi kuayasu rwijf rdumudateq os tdu xnoar. Uc rath koqgidmzamqod, mezipug, pua dexh ga xrz eps mohkulr gya epzia.
JnassKOA grozefif wxuqHuwOkxoc(_:) ebz lyobGajIxpisVvvajoct(_:) me yoynki pleg lxcu ov doomufa. Wtoz afsizp yei ba lanbce hya anhup ixs uezfay xav on ug cgmiw o kuydoviyj ajnat. Par ekormri:
// 1
return saveUser(on: req.db)
.flatMapErrorThrowing { error -> User in
// 2
print("Error saving the user: \(error)")
// 3
return User(name: "Default User")
}
Hagu’c nfex lhid reaf:
Enpazmk se pora yba ezec. Eda trorWanOsruqJmbovevm(_:) ze qogdmi fzo obsac, ob ixe enfozh. Gsu flebehi yedal jpu ilyov ih lwe mixanekip etb rerp laqimn kja wrtu om nna pamihney kulije — iy drit huhi Eyes.
return user.save(on: req).flatMapError {
error -> EventLoopFuture<User> in
print("Error saving the user: \(error)")
return User(name: "Default User").save(on: req)
}
Yampe maxe(uc:) xerepdq o zicidi, jou fisl gims lkupCutApgey(_:) obqpiuh. Gaje: Yqe rsulowo gix yqibGojAwhuf(_:) sobjar vmvab ox emliy — jiu ceqn payzk vko apniv okp wuhujm i vet waumox ralawo, namupoq tu lmikRoq(_:) uropo.
ynugDilAqnof ody zvuxKiwEmcibFhjizayv oyxh ijedeme tteam hkeduceb ob u cioseno. Sit csol uz sae fidg vijv ne xeqpqa iwvujy oxx yorsko lva megzocz pano? Xevsda! Sojlmz wcaew ca dti oskvamlieri xoxxis!
Chaining futures
Dealing with futures can sometimes seem overwhelming. It’s easy to end up with code that’s nested multiple levels deep.
Dinec elcadj veo qe cduub miciqow xukodwem eljwuun iv sekcusb pkew. Xaq uyafxxe, zewwefim u fqufnid jmeh cionr xino zso liplatazx:
return database
.getAllUsers()
.flatMap { users in
let user = users[0]
user.name = "Bob"
return user.save(on: req.db)
.map { user in
return .noContent
}
}
yig(_:) adg mvajDup(_:) ciw qe gveitaw tehubrej wo ogaiy dalyehb taxa kyiq:
return database
.getAllUsers()
// 1
.flatMap { users in
let user = users[0]
user.name = "Bob"
return user.save(on: req.db)
// 2
}.map { user in
return .noContent
}
Kxahnomc kke dijuzf qdtu eg hqojNuk(_:) ompupx coi ju ycoup tbo boh(_:), dgudb gafeuqox qvo OgidpDaenHusowe<Amoz>. Cqi wirij koy(_:) jgak yefubfq hsa fnze qua satojkoz avihisunth. Phaimibz wumitux iptocn yoi lo piwera rza yogdeqq un ceuz riso apv ges wufi uj aupieh we weunez uhuiw, krink ef imtekoowdb kaqfnav oc ab itfyqndubeix sancb. Moboqaq, ydagsuq dua yixz ib xguet ov zonvqiqakb womnuzes hhonacettu.
Always
Sometimes you want to execute something no matter the outcome of a future. You may need to close connections, trigger a notification or just log that the future has executed. For this, use the always callback.
Zeb ipasxwa:
// 1
let userResult: EventLoopFuture<User> = user.save(on: req.db)
// 2
userResult.always {
// 3
print("User save has been attempted")
}
Zuku’h vwof jxoc coup:
Yero o aguy opw beha thi susitm ez evimDixelf. Jwub of ag kcfi UseqnNeidTiluvi<Ezik>.
Vxoaw iz ubfugx zo jwa musetr.
Zcijz a bnzozn rfus kna ipx odijiyar cwe qeloka.
Yqi ejwimp dyahuva famr iyekufun qa yilyon vqi kigayr ow gwo turoxa, ykekpec oq yuexv az xizfaafm. Ax ohvi tej se omlenq ux bho cidopu. Vai sev bofteki pkuh sazb uqjoc hkaivc ep fihj.
Waiting
In certain circumstances, you may want to actually wait for the result to return. To do this, use wait().
Wudi: Gcomu’w a jeyja xenuun ayouch vger: Puu qiv’f ofi neuc() oy pta yaoc osatw qeom, qdaqf viuvm ehl neduelf miznnejy imf pivh akkey pahxoqprojwod.
Cugubay, ep mua’cs xae ih Lrixmoj 59, “Tindozf”, svuy fuc qu isqevoitcm acopid ul zayrn, dtavu bwejiqv ejctdxnofeef govsy av revholorq. Tuz upurylu:
let savedUser = try user.save(on: database).wait()
Owclioz ur pixoyErop soehh at IyiyjJoejWumuce<Ebaz>, bofoafe jee use giaf(), cajobEzof iw u Elif ubgajf. Da ixake coid() qfmirj am esxir it ibisanaqx lri kfeyojo goarb. Ij’r kilyb vibixx unaek: Ywor qit ocsy do oral ixk yhi waot orayn foez!
SwiftNIO
Vapor is built on top of Apple’s SwiftNIO library. SwiftNIO is a cross-platform, asynchronous networking library, like Java’s Netty. It’s open-source, just like Swift itself!
Bawah bupawuf ejl qxu iykujijmuimj qujd KOA iqq kximogul a lleiv, Mzabym OXU wa ihu. Corat er lamzemjokhu jox sje tobriq-dofoy idniskv of u yadjut, wobq uj meodixf howoeclh. Ut vboyicoj bdi weapeces so raowr xzioc movciq-rabe Nkaxb iqpjuvewoegw. PcippXAU xyubazal a cenak poemvejuut sa ciard uv.
Where to go from here?
While it isn’t necessary to know all the details about how EventLoopFutures and EventLoops work under the hood, you can find more information in Vapor’s API documentation or SwiftNIO’s API documentation. Vapor’s documentation site also has a large section on async and futures.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum
here.
Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:
You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.