Быстрая демонстрация

Синхронный код. Простой и лаконичный, но, пока он не завершится полностью, другие задачи не исполняются.

Выделить код

Код:

let downloadSync=
function( from, to ){
    try {
        let content= getContent( from )
        writeContent( to, content )
        alert( 'saved!' )
    } catch( e ){
        console.error( e )
    }
}

Асинхронный код. Пока идёт ожадание окончания запросов можно сделать ещё много других полезных вещей. Но структура кода трудна для восприятия так как представляет из себя хитрое переплетение функций.

Выделить код

Код:

let downloadAsync=
function( from, to ){

    getContent( from, whenContentReceived, onError )
    
    function whenContentReceived( content ){
        writeContent( to, content, whenContentWrited, onError )
    }
    
    function whenContentWrited( ){
        alert( 'saved!' )
    }

    function onError( e ){
        console.log( e )
    }
    
}

Цепочечный код. С помощью специального хелпера все функции и асинхронного варианта выстраиваются в одну линию. Обилие синтаксического шума и несовместимость со стандартными операторами управления потоком исполнения.

Выделить код

Код:

let downloadChain=
function( from, to ){
    Chain
    (   function( ){
            return getContent( from )
        }
    ,   function( content ){
            return writeContent( to, content )
        }
    ,   function( ){
            alert( 'saved!' )
        }
    ).fail(
        function( e ){
            console.log( e )
        }
    ).run()
}

Волокнистый код. Не останавливает исполнение других задач, но в то же время сохраняет линейную стуктуру подобно синхронному коду, почти не усложняя синтаксис.

Выделить код

Код:

let downloadFiber=
FiberCallback( function( from, to ){
    try {
        let content= yield getContent( from )
        yield writeContent( to, content )
        alert( 'saved!' )
    } catch( e ){
        console.error( e )
    }
} )

Подробнее о реализации

Сперва немного терминов..

Волокно - функция, имеющая следующий интерфейс:
* на вход она принимает 2 колбэка. первый должен быть вызван в случае успеха, единственным параметром ему передаётся вычисенный результат. второй вызывается в случае каких-либо ошибок, параметром ему передаётся объект исключения.
* колбэк должен быть вызван ровно один и ровно один раз
* она не должна выбрасывать никаких исключений - все ошибки должны приводить к выполнению второго колбэка

Простейшее волокно выглядит, например, так:

Выделить код

Код:

let Confirm= function( done, fail ){
    try {
        let choice= confirm( 'Are you ready?' )
        done( choice )
    } catch( exception ){
        fail( exception )
    }
}

Чтобы не писать каждый раз эти типовые try-catch, можно воспользоваться специальным хелпером:

Выделить код

Код:

let Confirm=
$fenix.Fiber( function( done, fail ){
    let choice= confirm( 'Are you ready?' )
    done( choice )
} )

Что если нам нужно, чтобы волокно могло принимать какие-то дополнительные параметры? Для этого можно воспользоваться замыканиями:

Выделить код

Код:

let Confirm=
function( message ){
    return $fenix.Fiber( function( done, fail ){
        let choice= confirm( message )
        done( choice )
    } )
}

Теперь Confirm - уже не само волокно, а фабрика волокон. Но постойте, код получился уж слишком сложным. Аналогичная безволоконная функция выглядит сильно проще:

Выделить код

Код:

let Confirm=
function( message ){
    let choice= confirm( message )
    return choice
}

Для упрощения создания волокон есть волшебный хелпер, преобразующий обычную функцию в фабрику волокон:

Выделить код

Код:

let Confirm=
$fenix.FiberThread( function( message ){
    let choice= confirm( message )
    return choice
} )

Но волшебство её не в этом, а в том, что она может приостанавливать своё исполнение в любой момент в ожидании завершения асинхронного вызова:

Выделить код

Код:

let AskUser=
$fenix.FiberThread( function( message ){
    let choice= yield Confirm( message )
    alert( 'Your choice is ' + choice ) 
} )

Чтобы приостановить исполнение нужно воспользоваться оператором yield, которому передать волокно (в примере оно создано фабрикой Confirm). Ввиду такого хитросплетения волокон такая функция и названа нитью. В данном случае реализация Confirm у нас синхронна, но ничто не мешает ей быть и асинхронной:

Выделить код

Код:

let Confirm=
function( message ){
    return $fenix.Fiber( function( done, fail ){

        formConfirm.elements.question.value= message

        formConfirm.elements.yes.onclick=
        formConfirm.elements.no.onclick=
        function( event ){
            try {
                let choice= ( event.target.value == true )
                done( choice )
            } catch( exception ){
                fail( exception )
            }
        }

    } )
}

И опять эти try-catch для обеспечения интерфейса волокон. Для упрощения работы с колбэками, служит специальный хелпер:


Выделить код

Код:

let Confirm=
$fenix.FiberThread( function( message ){

    let result= $fenix.FiberTrigger()

    formConfirm.elements.question.value= message
    formConfirm.elements.yes.onclick= result.done
    formConfirm.elements.no.onclick= result.done

    let[ event ]= yield result
    yield $fenix.FiberValue( event.target.value )

} )

Триггер - это волокно, предоставляющее 2 ручки - done и fail. Достаточно передать метод done в качестве колбэка, и с помощью yield подождать срабатывания триггера. В рузультате вы получите список аргументов переданных колбэку.

Важно отметить, что если внутри нити используется хотябы один yield, то интерпретатор не позволяет использовать return. Поэтому реализация FiberThread такова, что нить возвращает вызвавшему её коду результат последнего yield-а, а волокно FiberValue обеспечивает чтобы это было именно то значение, которое ему передали.

Но что если внутри волокна возникнет исключение? Например, мы забыли добавить в форму элемент question и не найдя его Confirm вывалился с исключением. В соответствии с интерфейсом волокон, будет вызван колбэк fail. Но, если волокно было вызвано с помощью yield из нити, то исключение можно перехватить стандартным способом:

Выделить код

Код:

let AskUser=
$fenix.FiberThread( function( message ){
    let choice
    try {
        choice= yield Confirm( message )
        alert( 'Your choice is ' + choice ) 
    } catch( exception ){
        alert( 'Can not ask user for choice' ) 
    }
} )

Если же никто исключение не перехватит - оно вывалится в консоль вместе со стрек-трейсом.

Быстрый старт

Выкачиваем фреймворк (https://github.com/nin-jin/fenix) и кладём куда-нибудь к себе в расширение. В chrome.manifest добавляем ресурс:

Выделить код

Код:

resource fenix fenix/

Третьим значением тут идёт относительный путь к директории с фреймворком.

Теперь где угодно вы можете получить корень фреймворка:

Выделить код

Код:

const $= Components.utils.import( 'resource://fenix/this.jsm', {} ).$

А с его помощью и доступ к модулям fenix (тут можно использовать как абсолютный путь, так и относительный):

Выделить код

Код:

const $fenix= $( 'resource://path/to/your/extension/modules/fenix/' )

Хотя корень и модули fenix лежат рядом, не стоит подключать модули так:

Выделить код

Код:

const $fenix= $( 'resource://fenix/' )

ибо если фреймворк будет используется несколькими расширениями, то вы не сможете быть уверенными, что модули берутся из вашей версии феникса.