{"id":128,"date":"2011-02-22T09:13:19","date_gmt":"2011-02-22T09:13:19","guid":{"rendered":"http:\/\/joose.it\/blog\/?p=128"},"modified":"2011-04-08T14:03:43","modified_gmt":"2011-04-08T14:03:43","slug":"joosex-cps-tutorial-part-ii","status":"publish","type":"post","link":"http:\/\/joose.it\/blog\/2011\/02\/22\/joosex-cps-tutorial-part-ii\/","title":{"rendered":"JooseX.CPS Tutorial &#8211; Part II"},"content":{"rendered":"<p>This is the 2nd part of the JooseX.CPS tutorial. Before reading it, make sure you&#8217;ve groked the <a href=\"http:\/\/joose.it\/blog\/2011\/02\/14\/joosex-cps-tutorial-part-i\/\" target=\"_blank\">1st part<\/a>. In the 1st part, we were managing the chains of anonymous functions. Such task can quickly become cumbersome, because the functions are, well, anonymous. Lets see how we can bring this concept to the higher level &#8211; out goal is to integrate the continuation passing style right into Joose classes.<\/p>\n<h1>Phase 1 &#8211; Synchronous<\/h1>\n<p>As the show case, lets use the following simple task. You have a  class &#8211; wrapper around the text file, which provides simple convenience  methods, like &#8220;read&#8221;, &#8220;write&#8221; etc. The wrapper is being used by Manager,  which has one method &#8211; `processFile`. This method takes the source file  name, reads the file and then saves the upper-cased content of the  source file to another file, appending `-res` to the filename. Skim the  code below:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">require('task-joose-nodejs')\r\n\r\nvar fs = require('fs')\r\n\r\nClass('TextFile', {\r\n\r\n    has : {\r\n        fileName    : { required : true },\r\n\r\n        data        : null\r\n    },\r\n\r\n    methods : {\r\n\r\n        asUpperCase : function () {\r\n            return this.data.toUpperCase()\r\n        },\r\n\r\n        read : function () {\r\n            return this.data = fs.readFileSync(this.fileName, 'utf8')\r\n        },\r\n\r\n        write : function (text) {\r\n            this.data = text\r\n\r\n            this.save()\r\n        },\r\n\r\n        save : function () {\r\n            this.saveAs(this.fileName)\r\n        },\r\n\r\n        saveAs : function (fileName) {\r\n            fs.writeFileSync(fileName, this.data)\r\n        }\r\n    }\r\n})\r\n\r\nClass('Manager', {\r\n\r\n    my : {\r\n\r\n        methods : {\r\n            processFile : function (fileName) {\r\n\r\n                var sourceFile = new TextFile({ fileName : fileName })\r\n\r\n                sourceFile.read()\r\n\r\n                var targetFile = new TextFile({\r\n                    fileName    : fileName.replace(\/\\.(\\w\\w\\w)$\/, '-res.$1')\r\n                })\r\n\r\n                targetFile.write(sourceFile.asUpperCase())\r\n            }\r\n        }\r\n    }\r\n})\r\n\r\ntry {\r\n    Manager.processFile('source.txt')\r\n} catch (e) {\r\n\r\n    console.log('Exception caught: %s', e)\r\n}<\/pre>\n<p>Everything operates synchronously, any exceptions can be caught with  `try\/catch` blocks and all is fine. Now save the code in some js file  and run it in node. You should see this:<\/p>\n<pre>Exception caught: Error: ENOENT, No such file or directory 'source.txt'<\/pre>\n<p>Of course, there is no source file. Create the &#8216;source.txt&#8217; in the  same directory as the code file and run it again. This time everything  should work and the resulting file &#8216;source-res.txt&#8217; , containing the  uppercased content of the source file should appear.<\/p>\n<p>Now, remove the write permission on the result file and run the script again. You&#8217;ll see<\/p>\n<pre>Exception caught: Error: EACCES, Permission denied 'source-res.txt'<\/pre>\n<p>Note, how we were able to caught the both exceptions from the  &#8220;processFile&#8221; method (1st one thrown from &#8220;read&#8221; and 2nd\u00a0 &#8211; from &#8220;saveAs&#8221;) without any additional gymnastic.<\/p>\n<p>Ok, lets re-write the code in asynchronous way, keeping the functionality the same.<\/p>\n<h1>Phase 2 &#8211; Asynchronous<\/h1>\n<p>So here&#8217;s how it will looks like (note though that we won&#8217;t catch &#8220;usual&#8221; exceptions &#8211; only those reported by NodeJS methods):<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">require('task-joose-nodejs')\r\n\r\nvar fs = require('fs')\r\n\r\nClass('TextFile', {\r\n\r\n    has : {\r\n        fileName    : { required : true },\r\n\r\n        data        : null\r\n    },\r\n\r\n    methods : {\r\n\r\n        asUpperCase : function () {\r\n            return this.data.toUpperCase()\r\n        },\r\n\r\n        read : function (callback, errback) {\r\n            var me = this\r\n\r\n            fs.readFile(this.fileName, 'utf8', function (err, data) {\r\n\r\n                if (err) {\r\n                    errback(err)\r\n                    return\r\n                }\r\n\r\n                me.data = data\r\n\r\n                callback(data)\r\n            })\r\n        },\r\n\r\n        write : function (text, callback, errback) {\r\n            this.data = text\r\n\r\n            this.save(callback, errback)\r\n        },\r\n\r\n        save : function (callback, errback) {\r\n            this.saveAs(this.fileName, callback, errback)\r\n        },\r\n\r\n        saveAs : function (fileName, callback, errback) {\r\n            fs.writeFile(fileName, this.data, function (err) {\r\n                if (err)\r\n                    errback(err)\r\n                else\r\n                    callback()\r\n            })\r\n        }\r\n    }\r\n})\r\n\r\nClass('Manager', {\r\n\r\n    my : {\r\n\r\n        methods : {\r\n            processFile : function (fileName, callback, errback) {\r\n\r\n                var sourceFile = new TextFile({ fileName : fileName })\r\n\r\n                sourceFile.read(function () {\r\n\r\n                    var targetFile = new TextFile({\r\n                        fileName    : fileName.replace(\/\\.(\\w\\w\\w)$\/, '-res.$1')\r\n                    })\r\n\r\n                    targetFile.write(sourceFile.asUpperCase(), callback, errback)\r\n\r\n                }, errback)\r\n            }\r\n        }\r\n    }\r\n})\r\n\r\nManager.processFile('source.txt', function () {}, function (e) {\r\n\r\n    console.log('Exception caught: %s', e)\r\n})<\/pre>\n<p>I hear you said &#8220;Piece of cake?&#8221;. How about such requirements then &#8211;  &#8220;write the content to the target file, and only if the operation  completed successfully, remove the source file. If there was an error  during `processFile` method, append it a log file. And process the array  of filenames in no more than 5 parallel threads&#8221;. If you say &#8220;thats 2 pieces of  cake&#8221; on this &#8211; then my congratulations, you are a callback&#8217;s <a href=\"http:\/\/www.flickr.com\/photos\/starfeeder\/2435922589\/\" target=\"_blank\">overlord<\/a> :). Otherwise, read further.<\/p>\n<h1>CPS conversion<\/h1>\n<p>So lets convert the classes above to the CPS. First thing to note is &#8211; we have &#8220;synchronous&#8221; (usual) and &#8220;asynchronous&#8221; methods.<\/p>\n<p>Synchronous methods returns the result immediately, with the `return`  statement. In the same way they throw the exceptions immediately, with  `throw`.<\/p>\n<p>&#8220;Asynchronous&#8221; methods returns the result, by passing it to the  callback, and they throws the exceptions by passing them to errbacks.  The call to callback\/errback may happen after some arbitrary delay.<\/p>\n<h2>Synchronous and asynchronous methods<\/h2>\n<p>Lets split the methods into 2 groups, by their synchronicity:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">require('task-joose-nodejs')\r\n\r\nvar fs = require('fs')\r\n\r\nClass('TextFile', {\r\n    trait : JooseX.CPS,\r\n    ...\r\n\r\n    \/\/ synchronous methods goes here\r\n    methods : {\r\n\r\n        asUpperCase : function () {\r\n            return this.data.toUpperCase()\r\n        }\r\n    },\r\n\r\n    \/\/ asynchronous methods goes here\r\n    continued : {\r\n\r\n        \/\/ right here\r\n        methods : {\r\n\r\n            read : function (callback, errback) {\r\n                ...\r\n            },\r\n\r\n            write : function (text, callback, errback) {\r\n                ...\r\n            },\r\n\r\n            save : function (callback, errback) {\r\n                ...\r\n            },\r\n\r\n            saveAs : function (fileName, callback, errback) {\r\n                ...\r\n            }\r\n        }\r\n    }\r\n})\r\n\r\nClass('Manager', {\r\n\r\n    my : {\r\n\r\n        trait   : JooseX.CPS,\r\n\r\n        continued   : {\r\n\r\n            methods : {\r\n\r\n                processFile : function (fileName, callback, errback) {\r\n                    ...\r\n                }\r\n            }\r\n        }\r\n    }\r\n})\r\n<\/pre>\n<p>The new class builder `continued` is provided by the trait  `JooseX.CPS`. It can contain the following properties:  &#8220;methods\/before\/after\/override&#8221; which has the same semantic as usual  builders. All asynchronous methods (or method modifiers) goes into that  section.<\/p>\n<h2>Magic continuation instance<\/h2>\n<p>Next thing to note &#8211; every asynchronous method has the &#8220;callback&#8221; and  &#8220;errback&#8221; arguments. Lets clean the definition and move them to the  special `this.CONT` symbol instead, which we&#8217;ll made available inside of each asynchronous method. Yes, its the continuation instance from the part 1. This magic symbol is something like `this.SUPER` we are all used to, but its not a function &#8211; its an object with own <a href=\"http:\/\/samuraijack.github.com\/JooseX-CPS\/doc\/html\/JooseX\/CPS\/Continuation.html\">methods and properties<\/a>.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">myAsynchronousMethod : function (p1) {\r\n\r\n    \/\/ this.SUPER calls the implementation of the current method\r\n    \/\/ from the superclass\r\n    this.SUPER\r\n\r\n    \/\/ this.CONT refers to the current continuation instance\r\n    this.CONT\r\n}<\/pre>\n<p>Important notes: <strong>Inside of each asynchronous method there is a special `this.CONT` symbol available, which refers to the &#8220;current&#8221; instance of JooseX.CPS.Continuation. When the method starts, the continuation is empty &#8211; it doesn&#8217;t contain any tasks.<\/strong><\/p>\n<p><strong>Then, when inside of any asynchronous method, you call any other asynchronous method &#8211; the call is converted into the task for current continuation (with its TRY method).<\/strong> For example:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\/\/ asynchronous methods section\r\ncontinued : {\r\n    methods : {\r\n\r\n        save : function () {\r\n            \/\/ call to another asynchronous method\r\n            this.saveAs(this.fileName).now()\r\n        },\r\n\r\n        saveAs : function () {\r\n            \/\/saveAs definition\r\n            ...\r\n        }\r\n    }\r\n}\r\n\r\n\/\/ the above gets translated to\r\n\r\n\/\/ asynchronous methods section\r\ncontinued : {\r\n    methods : {\r\n\r\n        save : function () {\r\n            this.CONT.TRY(function () {\r\n                \/\/saveAs definition\r\n                ...\r\n            }, this, [ this.fileName ]).now()\r\n        },\r\n\r\n        saveAs : function () {\r\n            ...\r\n        }\r\n    }\r\n}<\/pre>\n<p>Note, that call to TRY contains the object, calling the method, as 2nd argument and arguments for method &#8211; as 3rd.<\/p>\n<p>Asynchronous methods are prohibited to return any values with `return`. Instead they should return values using the call to `this.CONT.CONTINUE()` method (or throw the exceptions with `this.CONT.THROW()`). Or they can instead launch the nested continuation &#8211; in this case it will be responsible for returning values. <strong>In the &#8220;synchronous&#8221; meaning, asynchronous methods will return the continuation instance they are attached to.<\/strong><\/p>\n<p>Also, as you remember, continuation tasks won&#8217;t launch immediately &#8211; they needs to be activated with `now`.<\/p>\n<h1>Phase 3 &#8211; CPS<\/h1>\n<p>The full version of the example above will looks like:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">require('task-joose-nodejs')\r\n\r\nvar fs = require('fs')\r\n\r\nClass('TextFile', {\r\n\r\n    trait           : JooseX.CPS,\r\n\r\n    has : {\r\n        fileName    : { required : true },\r\n\r\n        data        : null\r\n    },\r\n\r\n    methods : {\r\n\r\n        asUpperCase : function () {\r\n            return this.data.toUpperCase()\r\n        }\r\n\r\n    },\r\n\r\n    continued : {\r\n        methods : {\r\n\r\n            read : function () {\r\n                var me          = this\r\n                var callback    = this.CONT.getCONTINUE()\r\n                var errback     = this.CONT.getTHROW()\r\n\r\n                fs.readFile(this.fileName, 'utf8', function (err, data) {\r\n\r\n                    if (err) {\r\n                        errback(err)\r\n                        return\r\n                    }\r\n\r\n                    me.data = data\r\n\r\n                    callback(data)\r\n                })\r\n            },\r\n\r\n            write : function (text) {\r\n                this.data = text\r\n\r\n                this.save().now()\r\n            },\r\n\r\n            save : function () {\r\n                this.saveAs(this.fileName).now()\r\n            },\r\n\r\n            saveAs : function (fileName) {\r\n                var callback    = this.CONT.getCONTINUE()\r\n                var errback     = this.CONT.getTHROW()\r\n\r\n                fs.writeFile(fileName, this.data, function (err) {\r\n                    if (err)\r\n                        errback(err)\r\n                    else\r\n                        callback()\r\n                })\r\n            }\r\n        }\r\n    }\r\n})\r\n\r\nClass('Manager', {\r\n\r\n    my : {\r\n        trait   : JooseX.CPS,\r\n\r\n        continued : {\r\n\r\n            methods : {\r\n                processFile : function (fileName) {\r\n\r\n                    var sourceFile = new TextFile({ fileName : fileName })\r\n\r\n                    sourceFile.read().THEN(function () {\r\n\r\n                        var targetFile = new TextFile({\r\n                            fileName    : fileName.replace(\/\\.(\\w\\w\\w)$\/, '-res.$1')\r\n                        })\r\n\r\n                        targetFile.write(sourceFile.asUpperCase()).now()\r\n\r\n                    }).now()\r\n                }\r\n            }\r\n        }\r\n    }\r\n})\r\n\r\nManager.processFile('source.txt').CATCH(function (e) {\r\n\r\n    console.log('Exception caught: %s', e)\r\n\r\n}).now()<\/pre>\n<p>Lets examine what have changed:<br \/>\n1) In the methods which integrates with &#8220;raw&#8221; asynchronous methods (&#8220;read&#8221; and &#8220;saveAs&#8221;) we&#8217;ve just removed the callbacks and errbacks from arguments and get them from the current continuation. Its a small winning as the outer interface of the methods becomes a bit simpler.<br \/>\n2) Methods which calls only other CPS methods (&#8220;write&#8221; and &#8220;save&#8221;) are now much cleaner &#8211; they don&#8217;t contain any callbacks at all.<br \/>\n3) The biggest gain is in the &#8220;processFile&#8221; method (which also uses only CPS methods). It doesn&#8217;t contain any callbacks\/errbacks now! And all the errors from it get caught correctly (try to launch the code w\/o source file and w\/o write permissions to result file).<br \/>\n4) We now catch the exceptions from `Manager.processFile` in much more straightforward syntax.<\/p>\n<p>For consolidation, lets see how the `processFile` method will look like on the JooseX.CPS.Continuation level:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">processFile : function (fileName) {\r\n\r\n    var sourceFile = new TextFile({ fileName : fileName })\r\n\r\n    this.CONT.TRY(function () {\r\n        \/* `read` method definition here *\/,\r\n    }, sourceFile, [])\r\n\r\n    this.CONT.THEN(function () {\r\n\r\n        var targetFile = new TextFile({\r\n            fileName    : fileName.replace(\/\\.(\\w\\w\\w)$\/, '-res.$1')\r\n        })\r\n\r\n        this.CONT.TRY(function () {\r\n            \/* `write` definition here *\/\r\n        }, targetFile, [ sourceFile.asUpperCase() ])\r\n\r\n        this.CONT.now()\r\n    })\r\n\r\n    this.CONT.now()\r\n}\r\n<\/pre>\n<h1>Some features<\/h1>\n<p>The fact that methods don&#8217;t launch immediately provides couple of interesting features.<\/p>\n<p>First of all, when you call several &#8220;continued&#8221; methods in a raw and then activate the continuation &#8211; the methods will be executed sequentially. Compare asynchronous and synchronous versions:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">asyncMethod : function () {\r\n    this.anotherAsyncMethod1()\r\n    this.anotherAsyncMethod2()\r\n    this.anotherAsyncMethod3()\r\n    this.anotherAsyncMethod4().now()\r\n}\r\n\r\nsyncMethod : function () {\r\n    this.anotherSyncMethod1()\r\n    this.anotherSyncMethod2()\r\n    this.anotherSyncMethod3()\r\n    this.anotherSyncMethod4()\r\n}<\/pre>\n<p>Such pattern works when methods do not depend on the returning values from the earlier methods.<\/p>\n<p>Another feature is that the call to asynchronous method can be used as some sort of &#8220;promise&#8221; (though JooseX.CPS was not designed to provide this functionality). You can pass the current continuation to some other method which will add some generic step to it. Using JooseX.CPS this way wasn&#8217;t intended but its possible.<\/p>\n<h1>Some sugar<\/h1>\n<p>A JooseX.CPS trait will add a <a href=\"https:\/\/github.com\/SamuraiJack\/JooseX-CPS\/blob\/master\/lib\/JooseX\/CPS\/ControlFlow.js\">JooseX.CPS.ControlFlow<\/a> role to your class, which allow you to omit the `this.CONT` when calling various methods of current continuation. So you can call &#8216;this.CONTINUE()` instead of `this.CONT.CONTINUE()`, `this.getCONTINUE()` etc.<\/p>\n<h1>Conclusion<\/h1>\n<p>Hopefully this post illustrates the ideas from <a href=\"..\/2011\/01\/13\/why-joose-part-2-phylosophical\/\">this one<\/a>. Note how the meta-layer absorbed all the low-level details and provided a much cleaner abstractions.<\/p>\n<p>JooseX.CPS significantly lowers the overhead of creation and managing the asynchronous interface of your classes. No excuses to write blocking code anymore :)<\/p>\n<h1>P.S.<\/h1>\n<p>Curious how the &#8220;2 pieces of cake&#8221; example will looks like in CPS? Here it is (or here download <a href=\"http:\/\/joose.it\/blog\/wp-content\/uploads\/2011\/02\/overlord.tar.gz\">from here<\/a>) . For brevity we avoided synchronization problems when writing to log file and just output to console. Try to run the code, already having one result file, w\/o write permission.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">require('task-joose-nodejs')\r\n\r\nvar fs = require('fs')\r\n\r\nClass('TextFile', {\r\n\r\n    trait           : JooseX.CPS,\r\n\r\n    has : {\r\n        fileName    : { required : true },\r\n\r\n        data        : null\r\n    },\r\n\r\n    methods : {\r\n\r\n        asUpperCase : function () {\r\n            return this.data.toUpperCase()\r\n        }\r\n\r\n    },\r\n\r\n    continued : {\r\n        methods : {\r\n\r\n            read : function () {\r\n                var me          = this\r\n                var callback    = this.CONT.getCONTINUE()\r\n                var errback     = this.CONT.getTHROW()\r\n\r\n                fs.readFile(this.fileName, 'utf8', function (err, data) {\r\n\r\n                    if (err) {\r\n                        errback(err)\r\n                        return\r\n                    }\r\n\r\n                    me.data = data\r\n\r\n                    callback(data)\r\n                })\r\n            },\r\n\r\n            write : function (text) {\r\n                this.data = text\r\n\r\n                this.save().now()\r\n            },\r\n\r\n            save : function () {\r\n                this.saveAs(this.fileName).now()\r\n            },\r\n\r\n            saveAs : function (fileName) {\r\n                var callback    = this.CONT.getCONTINUE()\r\n                var errback     = this.CONT.getTHROW()\r\n\r\n                fs.writeFile(fileName, this.data, function (err) {\r\n                    if (err)\r\n                        errback(err)\r\n                    else\r\n                        callback()\r\n                })\r\n            },\r\n\r\n            remove : function () {\r\n                var callback    = this.CONT.getCONTINUE()\r\n                var errback     = this.CONT.getTHROW()\r\n\r\n                fs.unlink(this.fileName, function (err) {\r\n                    if (err)\r\n                        errback(err)\r\n                    else\r\n                        callback()\r\n                })\r\n            }\r\n        }\r\n    }\r\n})\r\n\r\nClass('Manager', {\r\n\r\n    my : {\r\n        trait   : JooseX.CPS,\r\n\r\n        continued : {\r\n\r\n            methods : {\r\n\r\n                log : function (text) {\r\n                    console.log(text)\r\n\r\n                    this.CONTINUE()\r\n                },\r\n\r\n                processArray : function (fileNames) {\r\n\r\n                    Joose.A.each(fileNames, function (fileName) {\r\n\r\n                        this.AND(function () {\r\n\r\n                            this.processFile(fileName).except(function (e) {\r\n\r\n                                this.log(&quot;Error processing &quot; + fileName + &quot;: &quot; + e).now()\r\n\r\n                            }).now()\r\n                        })\r\n\r\n                    }, this)\r\n\r\n                    this.ANDMAX(5).now()\r\n                },\r\n\r\n                processFile : function (fileName) {\r\n\r\n                    var sourceFile = new TextFile({ fileName : fileName })\r\n\r\n                    sourceFile.read().andThen(function () {\r\n\r\n                        var targetFile = new TextFile({\r\n                            fileName    : fileName.replace(\/\\.(\\w\\w\\w)$\/, '-res.$1')\r\n                        })\r\n\r\n                        targetFile.write(sourceFile.asUpperCase()).andThen(function () {\r\n\r\n                            sourceFile.remove().now()\r\n                        })\r\n                    })\r\n                }\r\n            }\r\n            \/\/ eof methods\r\n        }\r\n        \/\/ eof continued\r\n    }\r\n})\r\n\r\nvar fileNames = [\r\n    'source1.txt',\r\n    'source2.txt',\r\n    'source3.txt',\r\n    'source4.txt',\r\n    'source5.txt',\r\n    'source6.txt',\r\n    'source7.txt'\r\n]\r\n\r\nManager.processArray(fileNames, &quot;log.txt&quot;).now()<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>This is the 2nd part of the JooseX.CPS tutorial. Before reading it, make sure you&#8217;ve groked the 1st part. In the 1st part, we were managing the chains of anonymous functions. Such task can quickly become cumbersome, because the functions are, well, anonymous. Lets see how we can bring this concept to the higher level [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[4],"tags":[12,16],"_links":{"self":[{"href":"http:\/\/joose.it\/blog\/wp-json\/wp\/v2\/posts\/128"}],"collection":[{"href":"http:\/\/joose.it\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/joose.it\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/joose.it\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/joose.it\/blog\/wp-json\/wp\/v2\/comments?post=128"}],"version-history":[{"count":40,"href":"http:\/\/joose.it\/blog\/wp-json\/wp\/v2\/posts\/128\/revisions"}],"predecessor-version":[{"id":278,"href":"http:\/\/joose.it\/blog\/wp-json\/wp\/v2\/posts\/128\/revisions\/278"}],"wp:attachment":[{"href":"http:\/\/joose.it\/blog\/wp-json\/wp\/v2\/media?parent=128"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/joose.it\/blog\/wp-json\/wp\/v2\/categories?post=128"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/joose.it\/blog\/wp-json\/wp\/v2\/tags?post=128"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}