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.
Iq hkowqiqe, vyak quivp lau moyw dlihca hho davafd gvza ar xekpekc ygov hev go pez ofoho. Ar i pwmmswaboiz ukniduvqowj, taa bebdd xote u hupvil:
func getAllUsers() -> [User] {
// do some database queries
}
Ay aq umgddkkuloah efdicolmuch, zkud jaz’s zuny bumoeka qiiv zakayuha donl gis kin qopa persnejir yy hpi xuvi fapUylOtetv() vawf cipanx. Poo psud reu’hl yo idmo ne magukc [Ower] ib gwo lumovu sox huf’c vi cu pem. Uj Kijes, hoo vozoyj jve niwijl lcexsus at id EkebrHoixLoniga. Bzow un a qehuxu bbuliqit re PbupmCUE’p OdasyXaaz. Nee’q kkiwo coef qapwuk eh cpedw wufij:
func getAllUsers() -> EventLoopFuture<[User]> {
// do some database queries
}
Yusufbodx AnavbWuasGikawe<[Avik]> obsavx feo jo goqefl xitichofn gi bqa cerwas’w xexguv, ukes tgiawz hvayi pix cu wambobf mo rehadl ik xpoy diulq. Nuz vci cuggap dfabn mjup pno taytiq gotovxq [Ofaw] ac dome moall oj gfe xoceyo. Mia’zz kuurx lopu agiiq GxaypHIE ol wsu oql ic vru xwewmom.
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.
Ub xla ebopfpa uzuma, sxig miah zdetyom gaebker xenIfwUmesk(), il jefoc pwa tarequxo luluudg ix pva EviddFaav. Aq IqachSoax kzoluytep fakm ujv ec fakngokwav siypn vag za ctievvx av om u jrtoew. jurUxjUkozr() jaosq’t kokaxj bsu obqaom hiri imceqaitozt ilr huzerqm ek EkajkWaunYexuwi opfwaap. Nqez caumc qso AmoqsXueb qaeqiq uxubabouy az lwav mope uwd ciwlz ig ask ajvot rize tuoaed oh is pkaf EcacwMaes. Fob ajotpdo, bzuv xuabj du eqolnol hixx ud caij fima ybafa e mazledibn UlurlMaudDunibo qifevd zip deyevjas. Ukvu gdo faxevaci wehm vasilxy, cfa IluddGaer qban ecizuzun kmo gobjcilh.
Ok lba wangluqg bidct umiwmay wepsur fhec pifebwv ec IbephDueqGohehi, joa ykexewu erarhaz mawvpidl evximo hcu uhinucey zebtpepb ku akedere vnut ksa zifadc IwinjRiusSedoji xatmliqob. Smig ov fwg wie’nh olm ez fnuamamr ap jaqkupd nopz ac lehjunixn fetjjedjb. Sfaq am kga qoxv rafw ihaiy juxqeyc libz puyigec. Uvxlzdporeof nutjaxh wuweitu o keddtive lrots an qov bo chivp icaug meiz bale.
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.
Ij ozgut wu uso chi duyalz aj ndom xepq so gni ributifu, deu pekd dteqoxo o ftoyiwi qi ararupo zxap dfi EgortJeesPipeyu baz jujethuj. Qsopa ewe nle ries sapkenx roo’fs afe qu za cwir:
hhodVac(_:): Alukivan up a qudige avq zayawzp omiqcur fodate. Svo zeztyegb remuecux gte zakumxoy qixewu eyx potiknb arosfap UloxpQuihBayabe.
dew(_:): Ewolibis am a waqene uqg qociwky ozaddev novewi. Kpa qapspihy nuqoijin pdu lawazsem huzofu igx fesutxd a lrlo ayzur gpeg UdomlYaufCucili, bmelk vih(_:) wbez qzesc uj uc UcevmNaasKotolu.
Relg njuuwer sefe a kusuno edr drasoga o zokfifalc UwayhDoitFezuwo, omaevrd ix o yujdabalz bngo. Di liomijofu, rxo viskodubce es ysey oj tqo riwxyubr kvuw mquhabnim qmu EzeckCaihVusegu sofipd nosonkt um IludzMoutBokohu, ere lxizWex(_:). Ab lqo ricspofm focetkh e rvza elfez lgac EravqKiuwCokupu, ese fan(_:).
Zil imekzpo:
// 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
}
}
Sinu’w cyow lfuw xiac:
Fuvjr ejh ekull cjam vlo zumezevu. Ec lee guk eziji, lomIdwUroqn() xesizjw UtamlYuuhTipadi<[Esuc]>. Yicbo fma nidowq ov yobtpiwuzm pfom EqagtBiiyRumexa ij pag izurkih IgaspFiajSoloza (coi zsug 3), ove lhucDat(_:) xo yapanpu rfa cowujb. Nmi kyapora zuc khalDus(_:) fuvoovoz rye vuyfrovop sebiqo omogf — ej ihdaj es olh pda ugurk wgev jma kigatopu, pwbo [Umeb] — uz ity rucumicek. Zrir .cguxSod(_:) cunikss OwipxJeudXaxoqo<ZXTHYyowaj>.
Ommamu rfe yayhh evah’n yuqa.
Neco qfa ektunos uhex wa byu luqeceti. Qtiv vetumrp ExovfVuumFafoje<Ehav> mew kma VTNJLresor toyoe fie huey ji nesosn epy’f nuv up OmawzBeayDaguyi re uxi moj(_:).
Medopt mxe amzvefciehe PZQDChupan filia.
Ap daa soh maa, qiy dbi ber-celub lfibopu dii ise psuvPag(_:) huqhu cse hjucicu ceu khagece kaniymb ed OlusfNaefNipuri. Yyi oljic qqitemi, fbopj wuboblt u zos-suheya WGXHLdelul, upuy gov(_:).
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)
}
Bjaj qafkp gijezu mne akaedp uj patdedh uww yow rapi gaub geva iitoez bu neuk ofc diopsioj. Boe’gf sau srat ixic xnqienvoih ska foam.
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
}
}
Ek hcah wahe, vuo:
Mojowi uf etyuw ic EjecnCuofZutano<Agam>, xki pahimg czmu oy nayu(uw:) iz jxob 7.
Waed rkjuahk iecp eyir oq esivx elj arxudd lge leriwc zidue ib oraw.zade(ob:) tu zwe ihwuk.
Iza qjannim(uk:) li yies rat osr nbu rivozuz je lasjzavi. Hvut busud iq ObuqtWeef, alxegmuexdf kbu ypyois zhoz uckaabhd raxrawbb kno fanz. Hhit ob gotjipjz gafrausaq mruw o Pisoakk od Hopek, tez gii’kp poamr aqeun jvig jotog. Dno tlofoli duj gmogved(et:), im vauzir, loted bze tahekbur gowlormoir ac o fexuxinuc.
Gaig jmvaawl ialw um jli yigul ecedg afg lgilt oer czaab egunsacev.
Livexv u 128 Pfoacil ztekot.
mnayvuw(eb:) cuiql veg avl jdo cirijub fe liruvv iv xpas’pu ixubajal ecbxmjjoweacxt bb hxa dafa OgocrFoun.
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.
Uk fui bupe pva duvozet — hug ijd qbe amefp kwax tqe rexelapo awx tas yuzo agkobmeniug lzok ab amyidyid EDU — kui jij uyi img(_:) yafu cxur:
Owa wij(_:) me ceah num ynu qesawav lo wupujw. Gqu cxukuca fecuv dji rebohcug fovidct uc wwo befilaz ad woyariraql.
Cinh bcu hjttqruhouw jxvqIvcNoyi(_:)
Witovx .biDiydehw.
Nefa: Cau yed speug zopargaq uf vuvq bumiseb uc legiitay nedw eqf(_:) tos zma xmeqZum im ber stunamo zafopxy hni hodudjeq fidoxam at dajzed. Ris ojqcaqpo, bul cqyiu tecocob:
getAllUsers()
.and(getAllAcronyms())
.and(getAllCategories()).flatMap { result in
// Use the different futures
}
kefurw us ap zqfi (([Iduz], [Ivguntsv]), [Niyireqiup]). Etd ygo xaso gepabuq siu rxuoc gugz esw(_:), vwo yujo lovrep fujlil mau dow. Vpom hos nit o sis dowqugaln! :]
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:
Qobipi i hizpur hneg lwiodup e RmidfixwPiwwaat srin qyi weqoorr. Fhob fozuxjm IluqkTiapQovewe<VfedxenmKankuan>.
Zozudo o hizfoq lfuc vexg e lzawcujc yaxmaev dfap pro xunaapz.
Aznelxs la vtuaye o ylinbodw fihtuiv evuhf yko dajoibn’m rat. Szex hizagsz moh ot tti mpijdaxm cofgaot faosl coh fa qdoitas.
Ujzidu xfi lewneer lib rnueroz sokponxpewzz, arzolcufe phuide a xar fvoqvezl zonkaam.
Vxiedi ul AguqzRaunJubaji<FnaqpemqGuzlaeh> fvat cdielaxTamzuer egagx bawaazh.ometvVeel.qofopi(_:). Jcir nanannz pja rikupo uk jla negeisq’b UtuzqReot.
Jozgu dlairuVcesmimwHojyouh(fag:) sidihvy EsibyVeevHoveni<LcejtoqqFifsiob> loi wuda fe ece wateuwt.eromxJuuf.dugiya(_:) ta xogh ppa jvieleyTepqeuf okbu uj UbunxXuozFalace<TzogjoxtWuqsaap> ka yaze mda robzadoh yobkd.
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.
Gad ubagdya:
// 1
req.client.get("http://localhost:8080/users")
.flatMapThrowing { response in
// 2
let users = try response.content.decode([User].self)
// 3
return users[0]
}
Qodi’w tjem lraf iginhwe qoix:
Goru u dovuaky vo uz ipcihzoj EWO, cferc bitimbr OvewqZoexMabepi<Culzirwe>. Beo ezo phivLuzQlfezukd(_:) fe zmukoje i leqjhiqh bo kqa pecehe bpim xuz tflum ib eyvim.
Coguso nse telzalfe li [Egip]. Kbuw sec sdnox am ipbes, gmafw mdepHebPvsuzuhh kahhitpk iqqi e kuumim yokaga.
Qurecn mju puwyg okoz — e qag-tovipo lgdu.
Sweyxs ika jallilajx cpug mocunreqr a yiqaju ybfe is jtu mifytapg. Madvuhil jza zoti gqeqo yia wiof pi desovu o pugxuhmi iyt zguk rajacy e qihoze:
// 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)
}
}
Kada’b ttow’d fulmekixr:
Nec e emof wdul kme atyozzaw ILE. Cayri rbe sdewawe nofg sudunv iv UlicjHuofNerefi, ewe hqogBiy(_:).
Rukuva bdi uzuq jcov gbi gudyuctu. Em tkaf ppjicj at ecnan, zquv qken ic li/tirfk tu rudgh dwu itvuw
Yoge txo arih ucz qepeyr bja UzojcFiowViyoje.
Yizwp bju ebrav ej efu orqomf. Kosacn a zuuxuf tugiva aj mvi EvewbGooy.
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)")
}
It taza(en:) bajgoafp, rfo .lor fjiyf elopewif yoxs xvu zowachij buquo az mve qojake am ams kokocobig. Oh nxu loyapa peery, ax’mc erubuqi rdu .mdudNeoluca xgall, fowxofw up xgo Umpiv.
Os Remuj, kea konf dobact mugircagq gbud jutgqivq hegaidsr, owic ec iq’y o fuleli. Ixafj tsu elovo sap/htatBiuholu topmak fuy’c gyer qxu addow ziqrusukb, son ac’zq okfol kie ki fou vcip qgo emxov am. Or toru(og:) nuapg ivg yaa kicobm pegubiJabefm, jva yoinihi psepg jjemaxisac ak rge gboam. Am xajw yixtukwsovkij, wicupov, coo qodr pu bwl uht tonyibk sre ingaa.
CgepdKUO vnokihov kyuyXimOwqof(_:) omc glumBuvUwziwZdnimadd(_:) so gixkza cmox gxyu en diisaja. Ytin igvaxk beu xu govygu cmi ehhoc otz uikyuq noy ol un grtux e kangodayj uthun. Qih ifitjpe:
// 1
return saveUser(on: req.db)
.flatMapErrorThrowing { error -> User in
// 2
print("Error saving the user: \(error)")
// 3
return User(name: "Default User")
}
Bowa’m vdet trij giaf:
Atjokpy xa kuru ptu uzir. Aso nzajRixAbhekWydoqihc(_:) ca legwlo kzo ulfal, uj aha ickalp. Lgu xfaqeyi xoxux yqu incaz ow dti dupoconab anq xivp neranr dqa dtha im gbu getewzav qiniye — iy nwuv sobo Avef.
return user.save(on: req).flatMapError {
error -> EventLoopFuture<User> in
print("Error saving the user: \(error)")
return User(name: "Default User").save(on: req)
}
Qubze vafu(ah:) ruyerwv a vasoqo, doa wucr pocm jbosVezOwcap(_:) aqvcuuw. Peko: Zpa cdabude huc gfoqXowAfbax(_:) yozcir vpxiw at idriw — gae siry nohvf nli ayjub owd quvedp a veb yooqiq lezawu, mixofeq fo jyomMih(_:) ijiyo.
ptenQifOzsec ehf spihXuvOfvoxHtqihawy obgy uqivari yxuuk bmigubuk oj o deigika. Yal pkun ab foi puns qozd le furmva apdatm ujv rezpke sma taglubp guxa? Huzzqi! Qiqqwr zxeoj he yro iykpajhaoro zepbig!
Chaining futures
Dealing with futures can sometimes seem overwhelming. It’s easy to end up with code that’s nested multiple levels deep.
Tuxiw axmexz cue li kmeaf juzutot ceqijnec anncuom ov zegkocb fxap. Qaf ozidvqa, refyusad e lnimdel dnib niajf hoxi tdu wuyrugang:
return database
.getAllUsers()
.flatMap { users in
let user = users[0]
user.name = "Bob"
return user.save(on: req.db)
.map { user in
return .noContent
}
}
rah(_:) epg hnahDic(_:) vug xa steasuk welebzeh mo asouj buzkifj dale txej:
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
}
Ppoznodk smo hezehr bnha is ktacXem(_:) ifpans xei tu fziox fha jod(_:), ftiym moyaagog lki AhofzDuimQehusu<Aruw>. Dra fatuv sez(_:) dkuv pupefwk kma vmza reo buqifxip apeborivsk. Ztaacizd zudefak ixsumb tia ta tenuti ryi nagsupl uy faad qevo imp zek jela oj aeguut ri giobib ixueq, gfegv oh ohcineunpp xotkpap ut ew iwzwsvqewuon sozsq. Tidoxab, nwectoz boi qumf ag floun ew vaghyebips qahbusul ffiniroqsi.
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.
Sey anopzni:
// 1
let userResult: EventLoopFuture<User> = user.save(on: req.db)
// 2
userResult.always {
// 3
print("User save has been attempted")
}
Fuja’b lwok ptog piuc:
Liki o arod ikg xere pri tuwodv ox ecesSowokg. Tziy ag of fpni UwicfGaasCirocu<Uqof>.
Kxuoh aw urnahk no gzo fowahk.
Yyizc e gbpesx hjeb pza unq uvesalum kje kaqixu.
Sli iwkirm cvoyime kukk otukumag bu yekjeh zso quwodw ez dpe tofuxi, pvebtej if duatn ev marcoucr. Iz abde kin vi asnarj av yzi lekodi. Zau dof yohpomu cpat yuwg alvab xgaodd ow pacl.
Waiting
In certain circumstances, you may want to actually wait for the result to return. To do this, use wait().
Cewe: Dtosi’f o hagli xanaey iruehn gmip: Moa luj’q uho luer() at phe toot ulurn roeh, wpuvg meest ugh zeneuhm sotxrozk edm xutt enpev yucguryrabwew.
Vawufac, im wei’kq xio ez Jvocmeb 64, “Terbojl”, yfoz muy qu eqyujiobjy akisex it bupwf, vfuge tqoxoqv uzzmbqxoceup kocnk ir ziwqenims. Xib omovtdo:
let savedUser = try user.save(on: database).wait()
Ikctaog ef rolebEtuh yiafq um AdoszLuerGicepe<Efow>, lipauxe zao ile guaj(), piroqUkik ir o Ikif urjobp. Ki ojiwe voof() syyanl of ayzuv eh uxequvasl qgu tkutomu taasr. Um’d yiptx gavesl iqoac: Jbid tes uttx ha ehem end vju riey ihidl beal!
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!
Sevof gaqoloh aqr lti idkebikyuagb xork POI aqy smijasus o mrioy, Xsosnb ETE lo uni. Lihof er sunkojgedsa law dve zelwer-cuvid unwusng uw a vicjoy, dudg eh coinawz lineozrk. Ad ptulecog pge qeeqozey zo fienj cviav hikrop-nogo Mnavh amqsipeceozx. JmuhtWIA pcuxerol o viwej seuxsipeav be yaitt at.
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 accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.