Asynchronous, yet readable, code
Kamil Witecki
November 16, 2016
Subject of discussion
(a) Callbacks
Subject of discussion
(a) Callbacks
(b) Promises
Subject of discussion
(a) Callbacks
(b) Promises
(c) Coroutines
Callbacks
Callbacks are simple but lacking. Do not use them.
Promises
Promises bring more control and structure to our code.
Coroutines
Coroutines provide best possible results and are our choice.
Writing code is easy
You write what you have on mind. And understand it.
https://en.wikipedia.org/wiki/Scribe#/media/File:Escribano.jpg
Maintenance is hell
https://en.wikipedia.org/wiki/Scribe#/media/File:Escribano.jpg [1]
Example
//avoid allocating temporary int
void countZeros ( std : : vector <void∗>& a , int ∗ out )
{
out = 0;
for ( auto i = begin ( a ) ; i != end ( a ) ; ++i ) {
i f (!∗ i ) ++out ;
}
}
Example
//avoid allocating temporary int
void countZeros ( std : : vector <void∗>& a , int ∗ out )
{
out = 0;
for ( auto i = begin ( a ) ; i != end ( a ) ; ++i ) {
i f (!∗ i ) ++out ;
}
}
template<typename Container >
auto c o u n t n u l l p t r s ( Container const& cont ) −>
std : : s i z e t
{
return count ( begin ( cont ) , end ( cont ) , n u l l p t r ) ;
}
Area of interest - asynchronous programming
Synchronous
Your code
Other code
Eg. kernel
Idle
Start operation
Complete operation
Start operation
Complete operation
Executes
other code
Operation results
wait for processing
Asynchronous
Your code
Other code
Eg. kernel
Case study: conversation I
We will focus on a example like:
e(d(c(b(a(params)))))
What in synchronous code gets as simple as:
e (d( c (b( a ( params ) ) ) ) )
Or:
resultFromA = a ( params )
resultFromB = b( resultFromA )
resultFromC = c ( resultFromB )
resultFromD = d( resultFromC )
resultFromE = e ( resultFromD )
Case study: conversation II
Case study: flow control
Next we will be looking into code that reads two files and compares content:
a = io.open ( ’a.txt’)
a data = a : read ( ’a’)
b = io.open ( ’b.txt’)
b data = b : read ( ’a’)
compare ( a data , b data )
Case study: debugging
Last but not least - we will be checking how errors are manifested.
Approach #1: callback
C++:
void r e a d h a n d l e r ( e r r o r c o d e const& ec ,
s i z e t bytes ) { . . . }
read ( . . . , r e a d h a n d l e r ) ;
Lua:
function r e a d h a n d l e r ( data ) end
socket : on ("data" , r e a d h a n d l e r )
JavaScript:
function r e a d h a n d l e r ( data ) {}
socket . on ("data" , r e a d h a n d l e r ) ;
Approach #1: pitfall
a ( params , function ( resultFromA , e r r )
i f ( ! e r r ) then return end
b( resultFromA , function ( resultFromB , e r r )
i f ( ! e r r ) then return end
c ( resultFromB , function ( resultFromC , e r r )
i f ( ! e r r ) then return end
d( resultFromC , function ( resultFromD , e r r )
i f ( ! e r r ) then return end
e ( resultFromD , function ( resultFromE , e r r )
// . .
end)
end)
end)
end)
end)
Approach #1: phony solution
a ( params , HandleResultsOfA ) ;
HandleResultsOfA ( resultFromA , e r r ) {
i f ( ! e r r ) return ;
b( resultFromA , HandleResultsOfB ) ;
}
HandleResultsOfB ( resultFromB , e r r ) {
i f ( ! e r r ) return ;
c ( resultFromB , HandleResultsOfC ) ;
}
HandleResultsOfC ( resultFromC , e r r ) {
i f ( ! e r r ) return ;
d( resultFromC , HandleResultsOfD ) ;
}
HandleResultsOfD ( resultFromD , e r r ) {
i f ( ! e r r ) return ;
Approach #1: how I see it
Approach #1: debugging I
1 f s . s t a t ("1.txt" , function () {
2 f s . s t a t ("2.txt" , function () {
3 //obvious error
4 f s . s t a t ( function (){} , function () {})
5 })
6 })
Approach #1: debugging II
Result is ok, error can be easily spotted:
>node t e s t . j s
f s . j s :783
binding . s t a t ( pathModule . makeLong ( path ) , req ) ;
ˆ
TypeError : path must be a s t r i n g
at TypeError ( n a t i v e )
at Object . f s . s t a t ( f s . j s :783:11)
at t e s t . j s : 4 : 8
at FSReqWrap . oncomplete ( f s . j s : 9 5 : 1 5 )
test.js:4 is:
f s . s t a t ( function (){} , function () {})
Approach #1: say goodbye to your stacktrace I
What if we use same function in multiple places?
1 var f s = r e q u i r e ( ’fs’ ) ;
2 function bug ( content , cont ) {
3 f s . w r i t e F i l e ("config.json" , content ,
4 function () { f s . s t a t ("config.json" , cont ) ;
5 } ) ;
6 }
7
8 bug ("{}" , "wrong")
Approach #1: say goodbye to your stacktrace II
Result:
>node t e s t . j s
throw new TypeError ( ’ c a l l b a c k must be a function ’ ) ;
TypeError : c a l l b a c k must be a f u n c t i o n
at makeCallback ( f s . j s : 7 8 : 1 1 )
at Object . f s . s t a t ( f s . j s :826:14)
at t e s t . j s : 4 : 2 0
at FSReqWrap . oncomplete ( f s . j s : 8 2 : 1 5 )
test.js:4 is:
function () { f s . s t a t ("config.json" , cont ) ;
While there error is in test.js:8:
bug ("{}" , "wrong")
Approach #1: What?
Approach #1: pros and cons
Pros
Easy to use,
Approach #1: pros and cons
Pros
Easy to use,
Integrated by default
Approach #1: pros and cons
Pros
Easy to use,
Integrated by default
Approach #1: pros and cons
Pros
Easy to use,
Integrated by default
Cons
Degrades to callback hell quickly
Approach #1: pros and cons
Pros
Easy to use,
Integrated by default
Cons
Degrades to callback hell quickly
Makes stacktraces unusable
Approach #1: pros and cons
Pros
Easy to use,
Integrated by default
Cons
Degrades to callback hell quickly
Makes stacktraces unusable
Does not address more complex flow control scenarios
Approach #1: pros and cons
Pros
Easy to use,
Integrated by default
Cons
Degrades to callback hell quickly
Makes stacktraces unusable
Does not address more complex flow control scenarios
Approach #1: pros and cons
Pros
Easy to use,
Integrated by default
Cons
Degrades to callback hell quickly
Makes stacktraces unusable
Does not address more complex flow control scenarios
Approach #2: Promises
Abstract model of asynchronous computation results.
Pending
.then(fulfillCallback,...)
.then(…, rejectCallback)
.catch()
Resolved
Rejected
Cancelled
OnValue
OnError
OnCancel
OnValue
OnError
(no actions)
(action)
(recovery)
(no recovery)
Approach #2: Case study I
Do you remember callback hell?
a ( params , function ( resultFromA , e r r )
i f ( ! e r r ) then return end
b( resultFromA , function ( resultFromB , e r r )
i f ( ! e r r ) then return end
c ( resultFromB , function ( resultFromC , e r r )
i f ( ! e r r ) then return end
d( resultFromC , function ( resultFromD , e r r )
i f ( ! e r r ) then return end
e ( resultFromD , function ( resultFromE , e r r )
// . .
end)
end)
end)
end)
end)
Approach #2: Case study II
Promises let you write it following way:
a ( params )
: next ( function ( resultFromA )
return b( resultFromA ) end)
: next ( function ( resultFromB )
return c ( resultFromB ) end)
: next ( function ( resultFromC )
return d( resultFromC ) end)
: next ( function ( resultFromD )
. . . end) .
Approach #2: Case study III
With a bit of imagination you can read it as:
a ( params )
--:next(function(resultFromA) return
b( resultFromA ) -- end)
--:next(function(resultFromB) return
c ( resultFromB ) -- end)
--:next(function(resultFromC) return
d( resultFromC ) -- end)
--:next(function(resultFromD)
. . . --end)
Approach #2: flow control
Promises are abstract model of computation. Therefore it is possible to build
flow control with them. For example:
c(a(paramsOfA), b(paramsOfB))
becomes:
l o c a l a = f s . readAsync ("a.txt")
l o c a l b = f s . readAsync ("b.txt")
d e f e r r e d . a l l ({a , b } ) : next ( function ( r e s u l t s )
return c ( r e s u l t s [ 0 ] , r e s u l t s [ 1 ] )
end )
Approach #2: What about callstacks? I
1 var Promise = r e q u i r e ("bluebird" ) ;
2 var f s = r e q u i r e ( ’fs’ ) ;
3 Promise . p r o m i s i f y A l l ( f s ) ;
4 function b( f i l e ) {return function ()
5 {return f s . statAsync ( f i l e )}}
6 f s . statAsync ("1.txt")
7 . then (b("2.txt" ))
8 . then (b( undefined ))
Approach #2: It’s a trap!
1 var Promise = r e q u i r e ("bluebird" ) ;
2 var f s = r e q u i r e ( ’fs’ ) ;
3 Promise . p r o m i s i f y A l l ( f s ) ;
4 function b( f i l e ) {return function ()
5 {return f s . statAsync ( f i l e )}}
6 f s . statAsync ("1.txt")
7 . then (b("2.txt" ))
8 . then (b( undefined ))
Approach #2: What about callstacks? II
Still not so good:
Unhandled r e j e c t i o n TypeError : path must be a s t r i n g
at TypeError ( n a t i v e )
at Object . f s . s t a t ( f s . j s : 7 8 3 : 1 1 )
at Object . t r y C a t c h e r ( node modules b l u e b i r d  j s  r e l e a s e  u t i l . j s : 1 6 : 2 3 )
at Object . r e t [ as statAsync ] ( e v a l at <anonymous> ( node modules b l u e b i r d  j s  r e l e a s e  p r o m i s i f y . j s : 1 8 4 : 1 2 ) , <a
at t e s t . j s : 5 : 1 3
at t r y C a t c h e r ( node modules b l u e b i r d  j s  r e l e a s e  u t i l . j s : 1 6 : 2 3 )
at Promise . settlePromiseFromHandler ( node modules b l u e b i r d  j s  r e l e a s e promise . j s : 5 0 9 : 3 1 )
at Promise . s e t t l e P r o m i s e ( node modules b l u e b i r d  j s  r e l e a s e promise . j s : 5 6 6 : 1 8 )
at Promise . s e t t l e P r o m i s e 0 ( node modules b l u e b i r d  j s  r e l e a s e promise . j s : 6 1 1 : 1 0 )
at Promise . s e t t l e P r o m i s e s ( node modules b l u e b i r d  j s  r e l e a s e promise . j s : 6 9 0 : 1 8 )
at Promise . f u l f i l l ( node modules b l u e b i r d  j s  r e l e a s e promise . j s : 6 3 5 : 1 8 )
at node modules b l u e b i r d  j s  r e l e a s e nodeback . j s : 4 2 : 2 1
at FSReqWrap . oncomplete ( f s . j s : 9 5 : 1 5 )
test.js:4-5
function b( f i l e ) { return function ()
{ return f s . statAsync ( f i l e )}}
Approach #2: pros and cons
Pros
Code structure and business logic are more coherent,
Approach #2: pros and cons
Pros
Code structure and business logic are more coherent,
Provide abstraction of flow control
Approach #2: pros and cons
Pros
Code structure and business logic are more coherent,
Provide abstraction of flow control
Approach #2: pros and cons
Pros
Code structure and business logic are more coherent,
Provide abstraction of flow control
Cons
Some boiler plate still needed for each call
Approach #2: pros and cons
Pros
Code structure and business logic are more coherent,
Provide abstraction of flow control
Cons
Some boiler plate still needed for each call
Makes stacktraces unusable
Approach #2: pros and cons
Pros
Code structure and business logic are more coherent,
Provide abstraction of flow control
Cons
Some boiler plate still needed for each call
Makes stacktraces unusable
Approach #2: pros and cons
Pros
Code structure and business logic are more coherent,
Provide abstraction of flow control
Cons
Some boiler plate still needed for each call
Makes stacktraces unusable
Approach #3: Coroutines
http://www.boost.org/doc/libs/1 60 0/libs/coroutine/doc/html/coroutine/intro.html [2]
Approach #3: Case study I
We will look, again, at e(d(c(b(a(params))))), now with coroutines!
coroutine.wrap ( function ()
resultFromA = a ( params )
resultFromB = b( resultFromA )
resultFromC = c ( resultFromB )
resultFromD = d( resultFromC )
resultFromE = e ( resultFromD )
end ) ( )
Approach #3: flow control
Coroutines allow to build flow control with them, too. For example:
c(a(paramsOfA), b(paramsOfB))
becomes:
coroutine.wrap ( function ()
a , b = c o r o s p l i t (
function () return f s. re ad As yn c ( ’a.txt’) end ,
function () return f s. re ad As yn c ( ’b.txt’) end)
c (a , b)
end ) ( )
Approach #3: What about callstacks? I
1 local f s = require ’coro-fs’
2 function bug ()
3 f s . s t a t ("2.txt")
4 return f s . s t a t ( function () print "x" end)
5 end
6 coroutine.wrap ( function () xpcall ( function ()
7 f s . s t a t ("1.txt")
8 bug ()
9 end ,
10 function ( e ) print ( debug.traceback ( e ) ) ; return e end)
11 end ) ( )
Approach #3: What about callstacks? II
> l u v i t . exe c o r o u t i n e s . lua
deps / coro−f s . lua : 4 4 : bad argument #1 to ’ f s s t a t ’
( s t r i n g expected , got f u n c t i o n )
stack traceback :
t e s t . lua : 1 0 : in f u n c t i o n <t e s t . lua :10>
[C ] : in f u n c t i o n ’ f s s t a t ’
deps / coro−f s . lua : 4 4 : in f u n c t i o n ’ bug ’
t e s t . lua : 4 : in f u n c t i o n ’ bug ’
t e s t . lua : 8 : in f u n c t i o n <t e s t . lua :6>
[C ] : in f u n c t i o n ’ x p c a l l ’
t e s t . lua : 6 : in f u n c t i o n <t e s t . lua :6>
coroutines.lua:6,8
coroutine.wrap ( function () xpcall ( function ()
bug ()
Approach #3: pros and cons
Pros
Resembles synchronous code,
Approach #3: pros and cons
Pros
Resembles synchronous code,
Provide abstraction of flow control,
Approach #3: pros and cons
Pros
Resembles synchronous code,
Provide abstraction of flow control,
Keeps stack untouched!
Approach #3: pros and cons
Pros
Resembles synchronous code,
Provide abstraction of flow control,
Keeps stack untouched!
Approach #3: pros and cons
Pros
Resembles synchronous code,
Provide abstraction of flow control,
Keeps stack untouched!
Cons
Some boiler plate still needed for each flow
Approach #3: pros and cons
Pros
Resembles synchronous code,
Provide abstraction of flow control,
Keeps stack untouched!
Cons
Some boiler plate still needed for each flow
Summary
(a) Callbacks
(b) Promises
(c) Coroutines
And the winner is!
Coroutine
Question and Answers
Bibliograpy I
B. Fisher.
Indiana jones and the temple of doom.
https://www.flickr.com/photos/7thstreettheatre/16587334520, 2015.
Cropped out ticket and release details. See licence:
https://creativecommons.org/licenses/by/2.0/ (Attribution 2.0 Generic (CC
BY 2.0)).
Oliver Kowalke.
http://www.boost.org/doc/libs/1 61 0/libs/coroutine/doc/html/coroutine/intro.ht
Distributed under the Boost Software License, Version 1.0. (See
accompanying file LICENSE 1 0.txt or copy at
http://www.boost.org/LICENSE 1 0.txt).

Kamil witecki asynchronous, yet readable, code

  • 1.
    Asynchronous, yet readable,code Kamil Witecki November 16, 2016
  • 2.
  • 3.
    Subject of discussion (a)Callbacks (b) Promises
  • 4.
    Subject of discussion (a)Callbacks (b) Promises (c) Coroutines
  • 5.
    Callbacks Callbacks are simplebut lacking. Do not use them.
  • 6.
    Promises Promises bring morecontrol and structure to our code.
  • 7.
    Coroutines Coroutines provide bestpossible results and are our choice.
  • 8.
    Writing code iseasy You write what you have on mind. And understand it. https://en.wikipedia.org/wiki/Scribe#/media/File:Escribano.jpg
  • 9.
  • 10.
    Example //avoid allocating temporaryint void countZeros ( std : : vector <void∗>& a , int ∗ out ) { out = 0; for ( auto i = begin ( a ) ; i != end ( a ) ; ++i ) { i f (!∗ i ) ++out ; } }
  • 11.
    Example //avoid allocating temporaryint void countZeros ( std : : vector <void∗>& a , int ∗ out ) { out = 0; for ( auto i = begin ( a ) ; i != end ( a ) ; ++i ) { i f (!∗ i ) ++out ; } } template<typename Container > auto c o u n t n u l l p t r s ( Container const& cont ) −> std : : s i z e t { return count ( begin ( cont ) , end ( cont ) , n u l l p t r ) ; }
  • 12.
    Area of interest- asynchronous programming Synchronous Your code Other code Eg. kernel Idle Start operation Complete operation Start operation Complete operation Executes other code Operation results wait for processing Asynchronous Your code Other code Eg. kernel
  • 13.
    Case study: conversationI We will focus on a example like: e(d(c(b(a(params))))) What in synchronous code gets as simple as: e (d( c (b( a ( params ) ) ) ) ) Or: resultFromA = a ( params ) resultFromB = b( resultFromA ) resultFromC = c ( resultFromB ) resultFromD = d( resultFromC ) resultFromE = e ( resultFromD )
  • 14.
  • 15.
    Case study: flowcontrol Next we will be looking into code that reads two files and compares content: a = io.open ( ’a.txt’) a data = a : read ( ’a’) b = io.open ( ’b.txt’) b data = b : read ( ’a’) compare ( a data , b data )
  • 16.
    Case study: debugging Lastbut not least - we will be checking how errors are manifested.
  • 17.
    Approach #1: callback C++: voidr e a d h a n d l e r ( e r r o r c o d e const& ec , s i z e t bytes ) { . . . } read ( . . . , r e a d h a n d l e r ) ; Lua: function r e a d h a n d l e r ( data ) end socket : on ("data" , r e a d h a n d l e r ) JavaScript: function r e a d h a n d l e r ( data ) {} socket . on ("data" , r e a d h a n d l e r ) ;
  • 18.
    Approach #1: pitfall a( params , function ( resultFromA , e r r ) i f ( ! e r r ) then return end b( resultFromA , function ( resultFromB , e r r ) i f ( ! e r r ) then return end c ( resultFromB , function ( resultFromC , e r r ) i f ( ! e r r ) then return end d( resultFromC , function ( resultFromD , e r r ) i f ( ! e r r ) then return end e ( resultFromD , function ( resultFromE , e r r ) // . . end) end) end) end) end)
  • 19.
    Approach #1: phonysolution a ( params , HandleResultsOfA ) ; HandleResultsOfA ( resultFromA , e r r ) { i f ( ! e r r ) return ; b( resultFromA , HandleResultsOfB ) ; } HandleResultsOfB ( resultFromB , e r r ) { i f ( ! e r r ) return ; c ( resultFromB , HandleResultsOfC ) ; } HandleResultsOfC ( resultFromC , e r r ) { i f ( ! e r r ) return ; d( resultFromC , HandleResultsOfD ) ; } HandleResultsOfD ( resultFromD , e r r ) { i f ( ! e r r ) return ;
  • 20.
  • 21.
    Approach #1: debuggingI 1 f s . s t a t ("1.txt" , function () { 2 f s . s t a t ("2.txt" , function () { 3 //obvious error 4 f s . s t a t ( function (){} , function () {}) 5 }) 6 })
  • 22.
    Approach #1: debuggingII Result is ok, error can be easily spotted: >node t e s t . j s f s . j s :783 binding . s t a t ( pathModule . makeLong ( path ) , req ) ; ˆ TypeError : path must be a s t r i n g at TypeError ( n a t i v e ) at Object . f s . s t a t ( f s . j s :783:11) at t e s t . j s : 4 : 8 at FSReqWrap . oncomplete ( f s . j s : 9 5 : 1 5 ) test.js:4 is: f s . s t a t ( function (){} , function () {})
  • 23.
    Approach #1: saygoodbye to your stacktrace I What if we use same function in multiple places? 1 var f s = r e q u i r e ( ’fs’ ) ; 2 function bug ( content , cont ) { 3 f s . w r i t e F i l e ("config.json" , content , 4 function () { f s . s t a t ("config.json" , cont ) ; 5 } ) ; 6 } 7 8 bug ("{}" , "wrong")
  • 24.
    Approach #1: saygoodbye to your stacktrace II Result: >node t e s t . j s throw new TypeError ( ’ c a l l b a c k must be a function ’ ) ; TypeError : c a l l b a c k must be a f u n c t i o n at makeCallback ( f s . j s : 7 8 : 1 1 ) at Object . f s . s t a t ( f s . j s :826:14) at t e s t . j s : 4 : 2 0 at FSReqWrap . oncomplete ( f s . j s : 8 2 : 1 5 ) test.js:4 is: function () { f s . s t a t ("config.json" , cont ) ; While there error is in test.js:8: bug ("{}" , "wrong")
  • 25.
  • 26.
    Approach #1: prosand cons Pros Easy to use,
  • 27.
    Approach #1: prosand cons Pros Easy to use, Integrated by default
  • 28.
    Approach #1: prosand cons Pros Easy to use, Integrated by default
  • 29.
    Approach #1: prosand cons Pros Easy to use, Integrated by default Cons Degrades to callback hell quickly
  • 30.
    Approach #1: prosand cons Pros Easy to use, Integrated by default Cons Degrades to callback hell quickly Makes stacktraces unusable
  • 31.
    Approach #1: prosand cons Pros Easy to use, Integrated by default Cons Degrades to callback hell quickly Makes stacktraces unusable Does not address more complex flow control scenarios
  • 32.
    Approach #1: prosand cons Pros Easy to use, Integrated by default Cons Degrades to callback hell quickly Makes stacktraces unusable Does not address more complex flow control scenarios
  • 33.
    Approach #1: prosand cons Pros Easy to use, Integrated by default Cons Degrades to callback hell quickly Makes stacktraces unusable Does not address more complex flow control scenarios
  • 34.
    Approach #2: Promises Abstractmodel of asynchronous computation results. Pending .then(fulfillCallback,...) .then(…, rejectCallback) .catch() Resolved Rejected Cancelled OnValue OnError OnCancel OnValue OnError (no actions) (action) (recovery) (no recovery)
  • 35.
    Approach #2: Casestudy I Do you remember callback hell? a ( params , function ( resultFromA , e r r ) i f ( ! e r r ) then return end b( resultFromA , function ( resultFromB , e r r ) i f ( ! e r r ) then return end c ( resultFromB , function ( resultFromC , e r r ) i f ( ! e r r ) then return end d( resultFromC , function ( resultFromD , e r r ) i f ( ! e r r ) then return end e ( resultFromD , function ( resultFromE , e r r ) // . . end) end) end) end) end)
  • 36.
    Approach #2: Casestudy II Promises let you write it following way: a ( params ) : next ( function ( resultFromA ) return b( resultFromA ) end) : next ( function ( resultFromB ) return c ( resultFromB ) end) : next ( function ( resultFromC ) return d( resultFromC ) end) : next ( function ( resultFromD ) . . . end) .
  • 37.
    Approach #2: Casestudy III With a bit of imagination you can read it as: a ( params ) --:next(function(resultFromA) return b( resultFromA ) -- end) --:next(function(resultFromB) return c ( resultFromB ) -- end) --:next(function(resultFromC) return d( resultFromC ) -- end) --:next(function(resultFromD) . . . --end)
  • 38.
    Approach #2: flowcontrol Promises are abstract model of computation. Therefore it is possible to build flow control with them. For example: c(a(paramsOfA), b(paramsOfB)) becomes: l o c a l a = f s . readAsync ("a.txt") l o c a l b = f s . readAsync ("b.txt") d e f e r r e d . a l l ({a , b } ) : next ( function ( r e s u l t s ) return c ( r e s u l t s [ 0 ] , r e s u l t s [ 1 ] ) end )
  • 39.
    Approach #2: Whatabout callstacks? I 1 var Promise = r e q u i r e ("bluebird" ) ; 2 var f s = r e q u i r e ( ’fs’ ) ; 3 Promise . p r o m i s i f y A l l ( f s ) ; 4 function b( f i l e ) {return function () 5 {return f s . statAsync ( f i l e )}} 6 f s . statAsync ("1.txt") 7 . then (b("2.txt" )) 8 . then (b( undefined ))
  • 40.
    Approach #2: It’sa trap! 1 var Promise = r e q u i r e ("bluebird" ) ; 2 var f s = r e q u i r e ( ’fs’ ) ; 3 Promise . p r o m i s i f y A l l ( f s ) ; 4 function b( f i l e ) {return function () 5 {return f s . statAsync ( f i l e )}} 6 f s . statAsync ("1.txt") 7 . then (b("2.txt" )) 8 . then (b( undefined ))
  • 41.
    Approach #2: Whatabout callstacks? II Still not so good: Unhandled r e j e c t i o n TypeError : path must be a s t r i n g at TypeError ( n a t i v e ) at Object . f s . s t a t ( f s . j s : 7 8 3 : 1 1 ) at Object . t r y C a t c h e r ( node modules b l u e b i r d j s r e l e a s e u t i l . j s : 1 6 : 2 3 ) at Object . r e t [ as statAsync ] ( e v a l at <anonymous> ( node modules b l u e b i r d j s r e l e a s e p r o m i s i f y . j s : 1 8 4 : 1 2 ) , <a at t e s t . j s : 5 : 1 3 at t r y C a t c h e r ( node modules b l u e b i r d j s r e l e a s e u t i l . j s : 1 6 : 2 3 ) at Promise . settlePromiseFromHandler ( node modules b l u e b i r d j s r e l e a s e promise . j s : 5 0 9 : 3 1 ) at Promise . s e t t l e P r o m i s e ( node modules b l u e b i r d j s r e l e a s e promise . j s : 5 6 6 : 1 8 ) at Promise . s e t t l e P r o m i s e 0 ( node modules b l u e b i r d j s r e l e a s e promise . j s : 6 1 1 : 1 0 ) at Promise . s e t t l e P r o m i s e s ( node modules b l u e b i r d j s r e l e a s e promise . j s : 6 9 0 : 1 8 ) at Promise . f u l f i l l ( node modules b l u e b i r d j s r e l e a s e promise . j s : 6 3 5 : 1 8 ) at node modules b l u e b i r d j s r e l e a s e nodeback . j s : 4 2 : 2 1 at FSReqWrap . oncomplete ( f s . j s : 9 5 : 1 5 ) test.js:4-5 function b( f i l e ) { return function () { return f s . statAsync ( f i l e )}}
  • 42.
    Approach #2: prosand cons Pros Code structure and business logic are more coherent,
  • 43.
    Approach #2: prosand cons Pros Code structure and business logic are more coherent, Provide abstraction of flow control
  • 44.
    Approach #2: prosand cons Pros Code structure and business logic are more coherent, Provide abstraction of flow control
  • 45.
    Approach #2: prosand cons Pros Code structure and business logic are more coherent, Provide abstraction of flow control Cons Some boiler plate still needed for each call
  • 46.
    Approach #2: prosand cons Pros Code structure and business logic are more coherent, Provide abstraction of flow control Cons Some boiler plate still needed for each call Makes stacktraces unusable
  • 47.
    Approach #2: prosand cons Pros Code structure and business logic are more coherent, Provide abstraction of flow control Cons Some boiler plate still needed for each call Makes stacktraces unusable
  • 48.
    Approach #2: prosand cons Pros Code structure and business logic are more coherent, Provide abstraction of flow control Cons Some boiler plate still needed for each call Makes stacktraces unusable
  • 49.
    Approach #3: Coroutines http://www.boost.org/doc/libs/160 0/libs/coroutine/doc/html/coroutine/intro.html [2]
  • 50.
    Approach #3: Casestudy I We will look, again, at e(d(c(b(a(params))))), now with coroutines! coroutine.wrap ( function () resultFromA = a ( params ) resultFromB = b( resultFromA ) resultFromC = c ( resultFromB ) resultFromD = d( resultFromC ) resultFromE = e ( resultFromD ) end ) ( )
  • 51.
    Approach #3: flowcontrol Coroutines allow to build flow control with them, too. For example: c(a(paramsOfA), b(paramsOfB)) becomes: coroutine.wrap ( function () a , b = c o r o s p l i t ( function () return f s. re ad As yn c ( ’a.txt’) end , function () return f s. re ad As yn c ( ’b.txt’) end) c (a , b) end ) ( )
  • 52.
    Approach #3: Whatabout callstacks? I 1 local f s = require ’coro-fs’ 2 function bug () 3 f s . s t a t ("2.txt") 4 return f s . s t a t ( function () print "x" end) 5 end 6 coroutine.wrap ( function () xpcall ( function () 7 f s . s t a t ("1.txt") 8 bug () 9 end , 10 function ( e ) print ( debug.traceback ( e ) ) ; return e end) 11 end ) ( )
  • 53.
    Approach #3: Whatabout callstacks? II > l u v i t . exe c o r o u t i n e s . lua deps / coro−f s . lua : 4 4 : bad argument #1 to ’ f s s t a t ’ ( s t r i n g expected , got f u n c t i o n ) stack traceback : t e s t . lua : 1 0 : in f u n c t i o n <t e s t . lua :10> [C ] : in f u n c t i o n ’ f s s t a t ’ deps / coro−f s . lua : 4 4 : in f u n c t i o n ’ bug ’ t e s t . lua : 4 : in f u n c t i o n ’ bug ’ t e s t . lua : 8 : in f u n c t i o n <t e s t . lua :6> [C ] : in f u n c t i o n ’ x p c a l l ’ t e s t . lua : 6 : in f u n c t i o n <t e s t . lua :6> coroutines.lua:6,8 coroutine.wrap ( function () xpcall ( function () bug ()
  • 54.
    Approach #3: prosand cons Pros Resembles synchronous code,
  • 55.
    Approach #3: prosand cons Pros Resembles synchronous code, Provide abstraction of flow control,
  • 56.
    Approach #3: prosand cons Pros Resembles synchronous code, Provide abstraction of flow control, Keeps stack untouched!
  • 57.
    Approach #3: prosand cons Pros Resembles synchronous code, Provide abstraction of flow control, Keeps stack untouched!
  • 58.
    Approach #3: prosand cons Pros Resembles synchronous code, Provide abstraction of flow control, Keeps stack untouched! Cons Some boiler plate still needed for each flow
  • 59.
    Approach #3: prosand cons Pros Resembles synchronous code, Provide abstraction of flow control, Keeps stack untouched! Cons Some boiler plate still needed for each flow
  • 60.
  • 61.
    And the winneris! Coroutine
  • 62.
  • 63.
    Bibliograpy I B. Fisher. Indianajones and the temple of doom. https://www.flickr.com/photos/7thstreettheatre/16587334520, 2015. Cropped out ticket and release details. See licence: https://creativecommons.org/licenses/by/2.0/ (Attribution 2.0 Generic (CC BY 2.0)). Oliver Kowalke. http://www.boost.org/doc/libs/1 61 0/libs/coroutine/doc/html/coroutine/intro.ht Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE 1 0.txt or copy at http://www.boost.org/LICENSE 1 0.txt).