The purpose: resolve the problem of ugly nested code
Problem: ugly nested code
- Async requires using callbacks instead of return value:
- For async function(e.g. make a api request)
A()
, we cannot get return value fromA()
immediately, but instead we have to pass a callback functioncb(ret)
to A, likeA(..., cb, ...)
, to let A to callcb(ret)
when A has done his async job
- For async function(e.g. make a api request)
- Sequential async functions bring in ugly nested code:
- if A, B, C are async functions, and we want the sequential execution order A -> B -> C -> D, then we have to pass B to A, C to B, D to C, which will be
A(...,()=>B(...,()=>C(...,D..)..)..)
, which are uglily nested especially when we are wrting B,C,D as anonymous functions
- if A, B, C are async functions, and we want the sequential execution order A -> B -> C -> D, then we have to pass B to A, C to B, D to C, which will be
No one can change above condition 1(callback rule) because using callback is the built-in way how JS achieve non-blocking IO with single thread VM(based on event-loop).
However, some smart guy invent the Promise object to solve condition 2 without changing condition 1: by remove callback parameters from function.
Take above async A->B->C->D example:
- Without promise object:
A(...,()=>B(...,()=>C(...,D..)..)..)
- With promise object:
let A = (...) => new Promise((succ, fail) => A(...,succ,..fail,..)) // remove callback from parameters
let B = (...) => new Promise((succ, fail) => B(...,succ,..fail,..))
let C = (...) => new Promise((succ, fail) => C(...,succ,..fail,..))
let a = A(..)
a.then(B(..)).then(C(..)).then(D(..))
a.then(x) // assume x,y are an existing Promise object
a.then(y)
// the execution order:
// A -> B -> C -> D
// -> X
// -> Y
The idea of Promise
- Use Promise to eliminate nested callbacks:
- by remove callback parameter from the original async function parameters
- to remove it, we create an (promise)object to store both original async function and its callback parameter position
- constructor will look like this:
new Promise((succ, fail) => {...async_func(..,succ,...fail..)})
- implement #then(succ_callback, fail_callback) to insert callbacks later
- by remove callback parameter from the original async function parameters
How to implement Promise
The basic idea is:
- allows
#then(on_fulfill, on_reject)
to insert followup callback on_fulfill:- we call original async function immediately by passing in a mockup_callback
- let
mockup_callback()
allows insert morefollowup_callback()
logics afterward- we use a mutable_queue to store followup_callbacks:
- the queue initially empty on object creation
-
#then(followup_callback)
will store the followup_callback into the queue -
mockup_callback()
will retrieve from queue and call all those followup_callback when the original async function actually callsmockup_callback()
,
- we use a mutable_queue to store followup_callbacks:
- allow chainable #then(): each
#then(on_fulfill, on_reject)
create a new promise object- means we store
res => succ(on_fullfill(res))
instead of simplyon_fulfill
as a followup_callback
- means we store
- allow pass in async logic into
#then()
, means that allowingon_fulfill()
to return a Promise object instead of a return value- in this case, we store
res => on_fulfill(res).then(succ)
as a followup_callback
- in this case, we store
In summary, a draft code will look like:
class MyPromise{
constructor(standard_async_func){ //standard_async_func has the same parameters format standard_async_func(followup_succ, followup_fail)
this.queue = []
let followup_succ = res => this.queue.forEach(followup => followup.succ(res))
let followup_fail = exp => this.queue.forEach(followup => followup.fail(exp))
standard_async_func(followup_succ, followup_fail)
}
then(onFulfill, onReject){
return new MyPromise((followup_succ, followup_fail) => {
// logic order:
// standard_async_func -> followup.succ -> onFulfill -> followup_succ
// standard_async_func -> followup.fail -> onReject -> followup_succ
let followup = {
succ: res => {
let ret
try { ret = onFulfill(res)
}catch(e){ return followup_fail(e) } // immediately return when catch
ret.then ? ret.then(followup_succ, followup_fail) : followup_succ(ret)
},
fail: exp => {
if(!onReject) return followup_fail(exp)
let ret
try { ret = onReject(exp)
}catch(e){ return followup_fail(e) } // immediately return when catch
ret.then ? ret.then(followup_succ, followup_fail) : followup_succ(ret)
}
}
this.queue.push(followup)
})
}
}
//test
let logging = callback => (r => {let ret = callback(r); console.log(ret); return ret})
let inc = logging(val => (val+5))
let dec = logging(val => (val-2))
function createTimeoutPromise(val, seconds){
return new MyPromise(succ => setTimeout((()=> succ(val)), 1000*seconds))
}
createTimeoutPromise(10, 2)
.then(inc)
.then(dec)
.then(dec)
.then(val => createTimeoutPromise(val, 3))
.then(inc)
.then(dec)
.then(dec)
网友评论