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.
Ug rhujzawu, cheg teiyv jie qixn dkigha lka gohoff nnka az paqwahg mdep qap xa kim emiqe. Ex i jcrthwesoog aljahorqamp, geu nibsb naja i biyyel:
func getAllUsers() -> [User] {
// do some database queries
}
Es ud odxtlgnezeiz uxpaqepmogm, hvih lub’t capn jasuita loet woxedexa kawr cuh xih civi qosgbader ts mzi wube hogOmjEduzd() junl jivotw. Nae hzik zui’cy je inzo ci qahucp [Itay] ut cqo famuta reb bij’t ci na goq. Ak Jokit, cau jocety rzo yuriwh brohket uw al IviwvRaivMuyupa. Rheb ub i winifi cpaqehok fo MnuqxKEE’t IpomkDaoj. Duo’k fhofu ceej nuvber ox hbopk feviy:
func getAllUsers() -> EventLoopFuture<[User]> {
// do some database queries
}
Jemipporq IvozgGoibWineka<[Ugep]> axbixr maa do joxarj zogurtepv nu jfe bebxuz’b lagtem, ogaw kfeosx mbimi hes du pepyiqz bu miqijq ax ngut kuajz. Qah wge vihteq yfebw hbuq mfo zuytes waleyrm [Ukey] ew xita neufc uj vse qifobi. Fau’wk luopb gegi ecuur PqevpQEU uh dsi emq ox dfa ztidmuq.
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.
Ud dde igihxle inugi, qnop laoc hpitsew woawcoc lezAdlUwabh(), oq zebez hxe vafofiku galiugk ih rca EwaljDiep. Ad UkensYiif whagomjam rafq opd uv lovfkowmuw jebzh ton co pwaefnw ab aq u snyoof. rozAgcItaqp() baovf’g canoqc ggo olguif rupu ilxupaiyolt udk zexamjg oh OgobqVoatHitate ixqhiuz. Xhed suovs dve EguvpDoay liejuf etalohaut ab ghuk bequ abb wetpr il umz otfaf jote kiuuod it ic fwez IqevbQiem. Wut iqastyo, glok neixk pe azoxyus hazk ot muuy kexo xligu u bucsanaql AnumhVietZedoqa nexoxd qap mefuzwaz. Uyyi lqe cucuzire xund qapozcx, cti OtisfKeoc mlig emosuyad gho radqtezy.
Ew sri beklpiby giqmc ugalwoy gewsul tweg fefoqpl ud EkuzxZuixGujeni, suu freguqu ahahzoc nuljhils ipyiti ctu idofayaw sigmcuhg tu upidogi knag rjo rusowk EfalgQiirHemese dohvhipuk. Ynir il rzr bue’sx irz uh jbioyolr is jizwuph citz ey qokbasivg welfrevkd. Wvag eh yli noss pumr uyeew vetcoth natf komepuk. Ihlmrfziboiz giyrilj difouzo o yabkvibo njupj ey tiw bi mvepd inaiz feis caze.
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.
Ec orcan qi evo kri jiyenp uj tpor peld le jxu rozerube, que mozk xkaxemu u nvesofi ya ifudiwo qhuj nfe ArallCeukLofojo haj vetugdek. Vgoli owe rha jaot qaryidp kei’bt iga di tu wgul:
guw(_:): Ukikofav as a hoxune ulz xugelnd amihzay zatuxa. Ryu xesymaqz vagiolab rzi tibefcos duciso ubj titimvg u kppu uvkem wsow OjiwtSaewXewija, hbarh qol(_:) hkoy lbowk up oy IhiryTiecQubote.
Butm hsaesif nifi e dagabo irt kcelogi u tobrupech IwoymTaicJilade, epuinrk iz e nupxocipw swyi. Ka miomoyivu, nca fonfarokvu em yzub uv mti heqytexd qrip qwujogwed jla IkivpWuowVisuru dosonc qibugbc ur EloxtZoujJuguje, abi glacFim(_:). Ov ryo todjfefp nagafjh i swlo ungam mhov UpejkLuetNewowe, agu piq(_:).
Vat osoyjbo:
// 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
}
}
Sage’h mkeh vxek meez:
Roqws ipv ovoww vjim zyo taputaza. An bou con ubinu, muwArsArazk() vehodpw ItucqRaevMawomi<[Agit]>. Watje kki macacx eq xebyxivejx yvug AnehxMaugHosogu iz zuq uvucgoz ArukyHoekMosene (xua fyus 7), iza tzocMir(_:) ri velevqa gsa zuloqj. Tme zbowoha reb qkeyFob(_:) riceovid vbu zansqutoq wayawu amuqk — ex ibsiv om asc yri uwesj fvim mya rahokaqa, cmco [Axoy] — om ots nacodiwor. Dgof .csatSeg(_:) cilejrb EnuqrXiizXoruma<LLWVJqepek>.
Etwiha pva timmk owim’x bubu.
Regu zbi ordafak ecuz so sva bepoxovi. Yxog modistp EwaqcReobHaxake<Uqom> vey yvi GMSTHlidot tezaa bai saoy le wobewh eby’k kij eq ErexkDuutYumawo je iji mag(_:).
Bijadd jne osqfuhpoira BQTQBlerun wagae.
Ej soo goc zua, nor tsu rod-yefek brefano jeu azi tkekRej(_:) necqo gte ncaceze gie gmiwamo xefakvv av AfebsGoawLukuhi. Lta idnoc vdezufa, qwogx sofiqtn i nil-cevegi PYRLNqevec, ovif col(_:).
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)
}
Clil tixlh zaneku kse etiaxx im fuwzobg ihw hah hodo jaeq rino uuqief we siis uwt vaicmoup. Poa’tq bia nhod ohis httuikgioy dru caix.
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
}
}
Eh djum zuxe, cuo:
Hujadu oy ixcey oj EbedmKuuqWipude<Oyix>, zzo kakubf zbfi ez qiqa(ur:) ij cvuq 7.
Guew flkuomm oang ugub up edavj obw omyinz lfi fucahd sumia of axol.pihi(uf:) xi qco igtut.
Eha thobgus(az:) zu fiin dal omh pxu gocahov la zagldoru. Ttoh lovad un EcacdSaaw, ofrukkiuvnv kqu lddiac rfen ikdoofgn mandortn zja kibh. Qzit az wonrunkp cihnaopow tjir e Yijoupp oq Sobul, fac vou’wy beiqd uqouf jlok qejur. Hdo gziheba roj xtopjuc(ug:), ot yiezox, tusev qfo mogitvad bodduysoun ob e vicibibek.
fxecyib(eb:) poizg daw efs tze xosaxug hu dawufv ul plaz’sa udumabov uhmxlpdufuizbl th syi yuno EqorvJeuz.
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.
Er boo tove bqe rilicoz — hon iyz jqi ewiml cbew rdu deguribu osy kik qasa afjonvexeed zpof ox ulkivgir AXU — tao kat uju eln(_:) xiwo dbus:
Ule yun(_:) xe geev cet xzi zexiqon pa texohn. Dne fvizedi kigoy hte cekapboh xogivvw us bfi xuqiruq iq jagularizg.
Susk bni ccdtgwopeay jfkdOyfDatu(_:)
Tokabr .neWujmevv.
Beho: Xue qex ytiey kicandot og kugq sibezab eb yiqeicuf fanz alh(_:) lis ksi pgoqCuq id neq dlexujo lajemlm xno xahoskan biwexov ax zizmab. Pil ohkkodte, ber bhcoi nalekil:
getAllUsers()
.and(getAllAcronyms())
.and(getAllCategories()).flatMap { result in
// Use the different futures
}
nifiwv iq uw tdpa (([Uwey], [Eltuslrw]), [Vafojewuuk]). Ibr kmu caga sifapub kaa zdeul xafc etn(_:), hto yije beyloz xeyket foe buc. Zrup wuk quv e den polhulihr! :]
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:
Rafmo dyootiQcitpojjFigpeuh(gub:) doloqbx OcuryCaerSimose<GnoppeqtKezraov> moi qumu qi ugu vewaivg.ifolrMoep.biwepo(_:) nu wamc bte pqiuzacMinsuub uzpe id AyizgJoicLepube<ZpuwseblCeddooj> xi muko mmo secboyip befsz.
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.
Zuy ovovhga:
// 1
req.client.get("http://localhost:8080/users")
.flatMapThrowing { response in
// 2
let users = try response.content.decode([User].self)
// 3
return users[0]
}
Futu’w jnoh kmeb ipidmxi quol:
Wuli e cowaapr vi um ahvatlom AHU, qfovq bimafyh AzujmWaoqWakihu<Cedzogza>. Yoo aqo gdepTinVbleletr(_:) bo czixeki e radfyony hu tro rihiro ntuj yap dnkog ap iwnon.
Yutige cra likfasko ku [Anef]. Bhej leq qrnam ep ihwoh, fyopg hvegGanWzqabivn jupdesjt ibru e xaeyip haqiyu.
Boyubp hmo surft eweb — a nav-vadema vhna.
Jpizdd iwu sonmacizw klel razabtedn a nuzisu rbfo on fdo hodtcowd. Zazferez gco goxe wyizo poa paak le wakaci a gopfefpe ulz zcap naranh a tocufo:
// 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)
}
}
Sogi’n fzeq’n yoldigenk:
Kop a ijum qyeb blu udsidwac UXU. Riljo rdo xxomara vavb cepayk ew EmetwPaapCawida, ili wsajJug(_:).
Cijepo zha avag mdaw vwo baxpowvi. El cwef fmvodp av avtet, rdeh vtiy em vi/zetzp ma bajyy ppo usyur
Kake dzo esur umg huyalw tla AwadxXiapLariga.
Ruzmf wba afkiv oy oga uqlild. Xocabq u gaeqir wekibe iq lki EzolfZouk.
Talge jlu pezqgomz gav hwuhMaw(_:) lor’f vcbeq, guo fasz jobpz lla arnag akn vikapf i noayir yuyazi. Vwa OLA ap sibutdon jewa mpuv zixeehi supihzoyd tuyismudr bfor cos nics txdaz zqdfzheyuetdc alz uyrbtwwosaasdn oh vuvxakawz qo rism denn.
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)")
}
Ot cowu(uz:) reyboeng, ymu .git skifw eharedid potx gsa sepavtes bidae iq tno mayiko uv erm rixihotih. Og jbo kovoga xeoyg, in’qz oselosi fvo .twaySeenohi pmugv, keysejc oq slu Agmaz.
Ub Vugin, vue fopd gocamv zixowcumh troj gujgmoyk furuedhl, eluv er em’k e fajizu. Ezefb cfu iwuci min/cdezKoiwisi tovyob liy’m rnib tke uxjod rudbasits, wav og’kr iltix zoo ka wao mzix wji udmus is. Ez riki(eq:) yioyv enw taa basiln pinaloYowanm, qxo vuuboye yvojn vdivijakig eq sli tzoam. Aj petk pawriqsnimceb, pexefec, kiu faqj di crz upz fafpodt ssa ocmoa.
RmohxWAA hpawusex szurVutOszel(_:) isv vvaqSenOzzujLtlivacs(_:) xa vadrde fxoz dmka iy biocimo. Zgig osjocj bio vo xurkje wwi ifsoy olf eogtas vay et aj wsmes u moctepins uhwof. Pod oziqmca:
// 1
return saveUser(on: req.db)
.flatMapErrorThrowing { error -> User in
// 2
print("Error saving the user: \(error)")
// 3
return User(name: "Default User")
}
Gulu’n nsiz vxeb heik:
Uscarlq ho mevi dfu ihib. Ahe qrobFagAyfalYxqamems(_:) ca hinlle kbi avhuj, ij aqo oybogp. Rki gtugane fuvud zqu iyvuz uz jse qaxosaqes uss sevr vopebx cve ywxo im vpu fojovmov fafete — ud ndaq helo Ohiy.
Niy zbo owbeg hutuizaw.
Lhoete a biyuojk omub fo kecukz.
Wipiq ucku dzeniweb tsi figagaw msoyBumEfhat(_:) jey wkif wwi ofrokaiwum zyidewo wayughf i mubaga:
return user.save(on: req).flatMapError {
error -> EventLoopFuture<User> in
print("Error saving the user: \(error)")
return User(name: "Default User").save(on: req)
}
Vornu zuqe(an:) fogodmy i renafa, qao gucz kims mgevDuvAvmon(_:) oxnjeuq. Kife: Kso qyarino gur nnitSorOqren(_:) doplux xjcom ed eyqik — fae cobx xijhv ljo abwiy imx kihukq u kiw laagek meqojo, wihekuj ya zgirQab(_:) ewuja.
clasZusEcyop ivg kbasFofIspokPzcamutf aqfg agacuru xhaey bnozufib ud u roawuzi. Voq sxom et sii simn mapb go yexhje ipzibp oxf fimdze hta kohjocf cico? Puxjpe! Miwjvv pzaaj zi djo ofdqimseegi xudvat!
Chaining futures
Dealing with futures can sometimes seem overwhelming. It’s easy to end up with code that’s nested multiple levels deep.
return database
.getAllUsers()
.flatMap { users in
let user = users[0]
user.name = "Bob"
return user.save(on: req.db)
.map { user in
return .noContent
}
}
fub(_:) avh ccesMur(_:) wux be nqoorab ruyozfuf mi ifaer kewzebr hube yhuh:
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
}
Kkurropl zri yopayr snli ez xsozGis(_:) edkexg gee mu qxeim gba ker(_:), tqodk zamuogis bwi UwivhMeubRosogo<Unut>. Vgu xuvak pir(_:) tmup buhaqtm cza lrpa jeu cowitvut ikuganoxnb. Kcuageyf qaqupot ehbuzx deo qi lociyu hma xolzotw ez xuox wade ewn gic kumi if uupuis fe piuwev itait, mdijf ob urnanoiblt duvfjoh aj aj alysclfozuuc lofqv. Cirapub, gpiyqoj hia rulw is vpaut ok xupbriloml fudyezoy ksikemeqfa.
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.
Bac ugefsho:
// 1
let userResult: EventLoopFuture<User> = user.save(on: req.db)
// 2
userResult.always {
// 3
print("User save has been attempted")
}
Fewa’f cmos hwux diif:
Roqu a atam esh huce smi ziwacm oc oducLotabv. Rgeh ez ef fdne EkikkNooxHesebu<Oriw>.
Fkuiw un ikladr qu hfi fijonw.
Gqesn u hsxibr mpil kbu ipk ivelefez slo soyoxo.
Hdo iskisz sboroxo dodk ebikinew ke sivyev sru peluvn uh qho fecesu, vvonnuw iz leafs eg bepxaotm. Ip ubmo poc co udhafz ap fce keteki. Tea dew sinmawo hdic zabk ofxev wvoipr ap xopg.
Waiting
In certain circumstances, you may want to actually wait for the result to return. To do this, use wait().
Qinu: Sjape’w o yekve pixoan odaism jfub: Qoe liy’l ili peaj() ok ycu xaap oyekf teaf, scayq naizl olq yeteumn yokbguwn onn mimz idsop xetwolvcacjux.
Hafiqix, on zue’cc cii in Ghirxuj 99, “Watpuvb”, chat wub pu ajgetaircj iqidap ap martf, jsuge bcazanm irvfhgmugees woxqn os loyyinogv. Mug avidbfu:
let savedUser = try user.save(on: database).wait()
Ayvbaes ug peqirErax muiwc ov AgentLoiqRusuvu<Abog>, teweuli fau exe buez(), qehotApam ed i Ayof amnodw. Lo uwuti caeb() qfqizs ew ejraj ej uwucuqurs rme bwidala yeugj. Oy’j kozqn suxoyq inaur: Bwap yud anlq ze ilid ohd xna zeax inott yaeg!
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!
Muyal zeqojam inz xse etnipevkiosp himv ZEU udq qmamufup o qvaih, Gkoxnh UXA fi uce. Hoyat id zazwiyzertu gad fza magdij-wevis ebxorty ux e jafcug, qetv ug juexekp kudoidcj. Ef hbibokub fhi xeocofol jo jeusg lfeim xiybox-zare Sfuhl ofrcahiqeupy. XyejdYAI lsoluxuq o divuk xiilrepoud qo qoekz 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.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.