npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

roku-promise

v0.2.1

Published

A Promise-like implementation for BrightScript/Roku

Downloads

28

Readme

Roku-Promise

A Promise-like implementation for BrightScript/Roku

build status monthly downloads npm version license Slack

This library helps making asynchronous logic simpler, by keeping invocation and result handling code all together in one place instead of littering your code with observer handlers that make code hard to follow.

Disclaimer: this only superficially looks like the JavaScript Promise API and is really only a tool to observe Nodes and handle changes asynchronously.

Installation

Using ropm

ropm install roku-promise

Manually

Copy source/Promise.brs into your project source/ folder

Basic Usage

createTaskPromise("TaskName", {
    input1: "some value",
    input2: 123
}).then(sub(task)
    results = task.output
    ' do other stuff here with the results.
    ' m is the original context from the caller
    m.label.text = results.text
end sub)

Behind the scenes, this is what happens:

  • A new Task object is created
  • Any properties provided are used to set the matching task fields
  • A dynamic observer is set on the signal field
  • The observer calls the then delegate, restoring the original scope/context

API

Task Promise

Create and run a task.

function createTaskPromise(taskName as string, fields = invalid as object, returnSignalFieldValue = false as boolean, signalField = "output" as string) as object

Arguments:

  • taskName - Task Node to create
  • fields - (optional) fields to set on the task
  • returnSignalFieldValue - (optional) return observed field value instead of the task itself
  • signalField - (optional) observed field name

Animation Complete Promise

Wait for an animation to complete.

function createOnAnimationCompletePromise(animation as object, startAnimation = true as boolean, unparentNode = true as boolean) as object

Arguments:

  • animation - an Animation node to observe
  • startAnimation - (optional) start the animation
  • unparentNode - (optional) detach the animation node when complete, if parented

Usage:

animation = m.top.animationName
createOnAnimationCompletePromise(animation, true, false).then(sub()
    ' animation completed!
    ' m is the original context from the caller
end sub)

Resolved Promise

To return a deferred value using a Timer node.

function createResolvedPromise(value as dynamic, delay = 0.01 as float) as object

Arguments:

  • value - payload of the Promise
  • delay - (optional) in seconds

Usage:

createResolvedPromise(someData).then(sub(value)
    ' a few ms later, do something with the `value`
    ' m is the original context from the caller
end sub)

Observable Promise

Create a Promise resolved by setting a Node field.

function createObservablePromise(signalFieldType = "assocarray" as string, fields = invalid as object, returnSignalFieldValue = false as boolean, signalField = "output" as string) as object

Arguments:

  • signalFieldType - (optional) type of the field
  • field - (optional) initial field values to set on the Node
  • returnSignalFieldValue - (optional) return observed field value instead of the Node itself
  • signalField - (optional) observed field name

Usage:

' setup
function create()
    promise = createObservablePromise()
    m.observedNode = promise.node
    return promise
end function

' caller
create().then(sub(node)
    ' do something with `node.output`
    ' m is the original context from the caller
end sub)

' later
m.observedNode.output = someData

Promise from Node

Observe a Node field.

function createPromiseFromNode(node as object, returnSignalFieldValue as boolean, signalField as string) as object

Arguments:

  • node - the Node context
  • returnSignalFieldValue - return observed field value instead of the Node itself
  • signalField - observed field name

Usage:

createPromiseFromNode(targetNode, "fieldName").then(sub(node)
    ' do something with `node.fieldName`
    ' m is the original context from the caller
end sub)

Manual Promise

Create a Promise resolved by calling resolve on it (no Node involved).

function createManualPromise() as object

Usage:

' setup
function create()
    m.promise = createManualPromise()
    return m.promise
end function

' caller
create().then(sub(value)
    ' do something with `value`
    ' m is the original context from the caller
end sub)

' later
m.promise.resolve(someData)

Features and limitations

  • The important bit is that, in the then delegate, the context is the same as the original caller. So if you call this from a Scene Graph component, m in the then delegate is the same m as the component. This allows you to easily use the results of the task by setting UI fields.

  • By the same token, since BrightScript does not have "capturing" closures, function-scoped variables are not available in the 'then` callback. Consider:

    sub SomeFunction(val1, val2)
        anotherVal = val1 + val2
        createTaskPromise("TaskName", {}).then(sub(task)
            ' m is available
            ' task is available
            ' val1, val2, and anotherVal are *not* available (they have different scope)
        end sub)
    end sub

    One work-around if you need to pass additional context to the callback is to pass the data as fields to the task:

    sub SomeFunction(val1, val2)
        anotherVal = val1 + val2
        createTaskPromise("TaskName", {
            val1: val1,
            val2: val2,
            anotherVal: anotherVal
        }).then(sub(task)
            ' m is available
            ' task is available
            ' task.val1, task.val2, task.anotherVal are now all available
        end sub)
    end sub
  • This implementation does not support chained promises. So you cannot do this:

    ' NOT valid
    createTaskPromise("TaskName", {}).then(callback).then(callback).then(callback)

    If you do need to react to the results of a task with another task call, you can nest promise calls like this:

    createTaskPromise("TaskName", {}).then(sub(task)
        results = task.output
        createTaskPromise("AnotherTask", {}).then(sub(task)
            otherResults = task.output
            m.label.text = otherResults.text
        end sub)
    end sub)
  • This implementation does not support multiple observers. So you cannot do this:

    ' NOT valid
    promise = createTaskPromise("TaskName", {})
    promise.then(callback1)
    promise.then(callback2) ' only last callback gets called
  • This implementation does not support late observers. So you cannot do this:

    ' NOT valid
    promise = createManualPromise()
    promise.resolve(value)
    promise.then(callback) ' already resolved - won't be called

Cook book

Although the most common use case is for spinning up, observing, and processing results from transient tasks, the library can be used in some other more advanced scenarios as well.

Accessing the underlying node

Node associated with a Promise can be accessed using their .node field.

promise = createResolvedPromise()
timer = promise.node

Cancelling a Promise

Promises can be cancelled by calling their .dispose() method - associated Node will be unobserved and callback deleted.

' setup
m.promise = createTaskPromise("TaskName", {})

' cancellation
if m.promise.node <> invalid
    m.promise.node.control = "STOP"
    m.promise.dispose()
end if

Long-lived Tasks

createTaskPromise creates a new Task each time it is called. For most uses, that is the desired behavior as it is simply providing syntactic sugar over the create/observer/react pattern usually used with tasks.

You may want to (re)use a long-lived task. Those tasks usually has a while loop that is processing incoming events on an roMessagePort.

The Promise library supports this pattern with the createObservablePromise method which should be used each time you need to run a job. The Promise's Node can be passed to the task to provide configuration and return the result.

' create a receiver Promise with specific result type ("node", "array"...) and payload
promise = createObservablePromise("assocarray", { itemId: 1234 })
promise.then(sub(node)
    result = node.output
    '...do stuff with the result
end sub)

' trigger a job in long-lived task
m.global.longLivedTask.get = promise.node

And then in your task:

while true
    msg = wait(0, m.port)
    if msg <> invalid
        msgType = type(msg)
        if msgType = "roSGNodeEvent"
            field = msg.getField()
            observer = msg.getData()
            if field = "get"
                '...do some stuff (make network calls, create ContentNodes, etc)
                result = DoStuff(observer.itemId)
                observer.output = result
            end if
        end if
    end if
end while

Repeated resolution

Usually, Promises can only be resolved once.

With this library you can set the suppressDispose flag (to a value <> invalid), so the observers won't be automatically disposed and fire repeatedly.

' setup
m.observer = createPromiseFromNode(targetNode, "fieldName")
m.observer.suppressDispose = true
m.observer.then(sub(node)
    ' called on every change
end sub)

' cleanup
m.observer.dispose()