Skip to content

playframe/promisync

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PlayFrame

PromiSync

0.7 kB Promises that Sync as you prefer

Installation

npm install --save @playframe/promisync

Description

PromiSync will create a Promise engine on top of any scheduling implementation. So you get to decide when your then, catch and finally handlers are going to execute and if try catch wrap is required. By buidling PromiSync on top of OverSync we get a Promise implemetation with frame rendering engine under the hood.

Should work well mixed with any other Promise implementation or await syntax. Please submit issues if any found

Usage

const oversync = require('@playframe/oversync')
const promisync = require('@playframe/promisync')
// let's add `decrypt` and `encrypt` stages to standard flow
const sync = oversync(performance.now, requestAnimationFrame,
  ['next', 'decrypt', 'catch', 'then', 'finally', 'encrypt', 'render'])

const CryptoPromiSync = promisync(sync)

CryptoPromiSync.Promise
  .resolve(secret)
  .decrypt(...)
  .then(...)
  .encrypt(...)
  .render(...)
  .frame(...)
  .catch(...)

Build your own Promise

In this section we will create something different

For example you just want lazy promises for better rendering performance by delaying heavy tasks. You could just do:

const later = (f)=> requestIdleCallback(f, {timeout: 500})
const Lazyness = promisync({
    then: later,
    catch: later,
    finally: later
  })
Lazyness.Promise
  .resolve(1)
  .then(...)
  .catch(...)
  .finally(...)

AWS.config.setPromisesDependency(Lazyness.Promise);

Or almost immediate, but framerate friendly Promise implementation:

const afterFrame = (f)=> requestAnimationFrame(=> setTimeout(f))
const Framer = promisync({
      then: afterFrame,
      catch: afterFrame,
      finally: afterFrame,
      render: requestAnimationFrame
  })
Framer.Promise
  .resolve(
    // fetch and JSON parse are happening lazy on idle
    Lazyness.then(()=> fetch(...))
      .then((body)=> body.json())
  )
  // Framer's `then` will wait for current frame to render first
  .then(updateState)
  // `render` is part of Framer's promise chain
  .render((state, ts)=> updateDom(state))
  // if anything goes wrong
  .catch(...)

Look how much control over execution flow we gained by just using promises

And now the most aggressive Promise implemetation but with exception recovery

const trySyncronously = (f)=> try{f()} catch(e){f.r(e)}
const PromiSync = promisync({
      then: trySyncronously,
      catch: trySyncronously,
      finally: trySyncronously
})
PromiSync.Promise.resolve(1)
  .then(...)
  .then(...)
  .then(...)
  .catch(...)
  .then(()=> console.log('chained')) // This logs first

console.log('syncronously') // This logs second

Annotated Source

Importing @playframe/proxy

proxy = require '@playframe/proxy'

Cheaply marking any value as rejected

REJECTED = Symbol 'REJECTED'
mark_rejected = (error)=>
  error = Object error # Object wrapper for primitives
  error[REJECTED] = true
  error

Defining a higher order function that takes a prototype sync for our future promise chain. sync needs only to implement the scheduling and try catch if needed. Methods catch and finally behave in Promise manner

module.exports = (sync)=>

Lets use a tiny proxy implementation for creating trapped objects with the same methods as sync

  methods = Object.keys sync
  make_proxy = proxy methods

chained is a higher order function that takes a schedule function and handler f to wrap f into chain resolver and pass it to schedule. It returns a proxy object methods of which will be executed after f is resolved

  chained = (schedule)=>(f)=>

Please note that closures are _prefixed

    _done = false
    _result = null
    _chain = ID

    resolve = (result)=>
      unless _done
        _done = true
        schedule _chain
        _result = result

    reject = (error)=> resolve mark_rejected error

    schedule wrap = (a...)=>
      unless _done
        result = f a...
        if result and result.then
          result.then resolve, reject
        else
          resolve result
        result

    wrap.r = reject

    make_proxy (method, f, recover)->
      if recover
        return @_h(method, f).catch recover

      fill = if method is 'finally'
        (x)=> f x; _result

      else if method is 'catch'
        (x)=>
          if _result[REJECTED]
            _result[REJECTED] = false
            f _result, x
          else
            _result
      else
        (x)=>
          if _result[REJECTED]
            _result
          else
            f _result, x

      # ✌️ combinator for nested chains
      chained((fill)=>
        if _done
          sync[method] fill
        else
          # chain of closures to call later
          # `do` does `_chain` closure
          # and returns the second `=>`
          _chain = do (_chain)=>=>
            _chain()
            sync[method] fill
            return
      ) fill

Now lets copy all methods from sync into returned chain object by wrapping them in chained. Also lets define Promise property of our chain object.

  chain = methods.reduce ((chain, m)=>
    chain[m] = chained sync[m]
    chain
  ),
    Promise: Promise = (f)=>
      _fill = ID
      
      p = chained((fill)=>
        _fill = fill
      ) ID
      
      # f(resolve, reject)
      f _fill, (x)=> _fill mark_rejected x
      p

  Promise.resolve = (x)=> chain.then => x
  Promise.reject = (x)=> chain.catch => mark_rejected x

  Promise.race = (list)=> Promise (resolve, reject)=>
    {length} = list
    while length--
      list[length].then resolve, reject
    return

  Promise.all = (list)=> Promise (resolve, reject)=>
    {length} = list
    i = 0
    arr = Array length
    while i < length
      list[i].then (do (i)=>(x)=> # i closure
        arr[i] = x
        resolve arr unless --length
      ), reject
      i++
    return

  chain


# Identity function
ID = (x)=> x

About

0.7 kB Promises that Sync as you prefer

Resources

License

Stars

Watchers

Forks

Packages

No packages published