Edit in JSFiddle

var myStorage = this.myStorage = new Class({
    // as a mixin or standalone class that can cache anything into local storage.
    // in this case, we want to use this in Request.JSONP and have it cache replies 
    // from the twitter API

    storage: {}, // you can puncture this through classname.storage[key]

    Implements: [Options],

    options: {
        storageMethod: "sessionStorage", // or localStorage
        privateKey: "myStorage" // sub key for namespacing
    },

    initialize: function(options) {
        this.setOptions(options);
        this.storageMethod = this.options.storageMethod;
    },

    setupStorage: function() {
        this.hasNativeStorage = !!(typeOf(window[this.storageMethod]) == "object" && window[this.storageMethod].getItem);
        if (this.hasNativeStorage) {
            try {
                this.storage = JSON.decode(window[this.storageMethod].getItem(this.options.privateKey)) || this.storage;
            }
            catch(e) {
                // session expired / tabs error (security)
            }
        }
    },

    getItem: function(item) {
        return this.storage[item] || null;
    },

    setItem: function(item, value) {
        this.storage = JSON.decode(window[this.storageMethod].getItem(this.options.privateKey)) || this.storage;
        this.storage[item] = value;
        if (this.hasNativeStorage) {
            try {
                window[this.storageMethod].setItem(this.options.privateKey, JSON.encode(this.storage));
            }
            catch(e) {
                // session expired / tabs error (security)
            }
        }
    },

    removeItem: function(item) {
        delete this.storage[item];
        if (this.hasNativeStorage) {
            try {
                window[this.storageMethod].setItem(this.options.privateKey, JSON.encode(this.storage));
            }
            catch(e) {
                // session expired / tabs error (security)
            }
        }
    }
});

// allow storage in Request.JSONP first - as mixin. 
// All instances of Request.JSONP can now benefit from storage class
// Request.JSONP.implement(new myStorage);

// sample JSONP class that will prefer storage to live data
Request.twitterUSER = new Class({
    
    Extends: Request.JSONP,
    
    Implements: myStorage,

    options: {
        url: "http://api.twitter.com/1/users/show/{user}.json"
    },

    initialize: function(user, options) {
        this.user = user; // will be used as key in storage object
        this.parent(options);
        this.setupStorage(); // initialize storage
        this.options.url = this.options.url.substitute({user: user});
    },

    send: function(options) {
        options = options || {};
        // override default send - first check if result under key this.user in storage
        var data = this.getItem(this.user);
        if (data && !options.freshen) {
            // if yes, then fire the oncomplete/success immediately with the data as argument
            this.fireEvent("success", data);
            this.fireEvent("complete", data);
            this.fireEvent("retrieve", data);
            return;
        }
        // otherwise - we use normal send.
        this.parent(options);
    },

    success: function(args) {
        // store result into storage for later.
        var data = Array.prototype.slice.call(args)[0];
        this.setItem(this.user, data);
        this.fireEvent("store", data);
        this.parent(args);
    }
    
});

(function() {
    var testRequest = new Request.twitterUSER("shitmydadsays", {
        onRequest: function() {
            document.id("status").set("html", "getting data...");
        },
        onComplete: function(data) {
            var el = document.id("userData");
            el.removeClass("hide");
            data.text = data.status.text;
            el.set("html", decodeURI(el.get("html")).substitute(data)).removeClass("hide");
        },
        onStore: function(value) {
            document.id("status").set("html", "No cache, stored data for {user}".substitute(this));
        },
        onRetrieve: function(value) {
            document.id("status").set("html", "Retrieved from cache for {user} - no request sent (API rate saving) + performance".substitute(this));
        }
    });

    document.getElements("a.send").addEvent("click", function(e) {
        e.stop();
        document.id("userData").addClass("hide");
        testRequest.send(this.hasClass("freshen") ? {freshen:true} : null)
    }); 
})();
<a class="send" href="#">Issue a normal JSONP request - cached or live</a> | <a href="#" class="send freshen">Force refresh from twitter API</a>
<div id="status" class="baseBox shadowy">waiting... make sure you load it twice to see the caching</div>
<div id="userData" class="hide baseBox shadowy">
    <img src="{profile_image_url}" class="pimage shadowy" />
    <!-- <div style="background:url({profile_image_url}) no-repeat right top"> -->
        <div class="left legend">Twitter nick:</div>
        <div class="left"><a href="http://twitter.com/{screen_name}">@{screen_name}</a></div>
        <div class="clear"></div>
    
        <div class="left legend">Full name:</div>
        <div class="left">{name}</div>
        <div class="clear"></div>
    
        <div class="left legend">Tagline:</div>
        <div class="left" style="width: 550px;">{description}</div>
        <div class="clear"></div>
        
        <div class="left legend">Location:</div>
        <div class="left">{location}</div>
        <div class="clear"></div>
    
        <div class="left legend">Homepage:</div>
        <div class="left"><a href='{url}'>{url}</a></div>
        <div class="clear"></div>
        
        <div class='left legend'>Followers / follows:</div>
        <div class="left">{followers_count} / {friends_count}</div>
        <div class="clear"></div>
        
        <div class="left legend">Last update:</div>
        <div class="left" style="width: 550px;"><em>{text}</em></div>
        <div class="clear"></div>
        
    <!-- </div> -->
</div>


<br />
Real world example: <a target=_blank href="http://dci.uk.net/trends/mootools">twitter hashtag trends thingie</a><br>
Using storage for: <ul>
<li>followers / profile info</li>
<li>bit.ly and other shortened URLs resolution</li>
<li>ajax search</li>
<li>marking read tweets, votes and reports</li>
</ul>
If no storage is available, caching only lasts until a page reload. 
<h1>Pitfalls</h1>
Because of the fallback to object properties and the namespacing, your 'key' needs to be a valid object property.<br />
Hence, you cannot do <strong>instance.setItem("http://bit.ly/kQHzsm", someData)</strong> with the URL as key.<br/><br/>
Instead, make sure you normalise it first, for example:<br/>
<strong>instance.setItem("http://bit.ly/kQHzsm".replace(/[^a-zA-Z0-9]+/g,''), someData);</strong> will work.<br/>
This results in "httpbitlykQHzsm" so eg, instance.storage[instance.options.privateKey].httpbitlykQHzsm == someData.

html {
    font-family: verdana;
    font-size: 12px;
}
div.left {
    float: left;
}

div.clear {
    clear: both;
}

div.legend {
    width: 150px;
    font-weight: bold;
}
#status {
    color: #fff;
    margin: 5px 0 15px 0;
}
.hide {
    display: none;
}

em {

}

div.baseBox {
    color: #B3AF74;
    border-bottom: 1px solid #000;
    background-color: #500;
    background-image: -moz-linear-gradient(top, #700,#500);
    background-image: -webkit-linear-gradient(top, #700,#500);
    background-image: -o-linear-gradient(top, #700,#500);
    padding: 5px;
    border: 1px solid #000;
}

img.pimage {
    position: absolute;
    border: 2px solid #000;
    right: 50px;
    margin-top: -10px;
    -moz-transform: skew(7deg) rotate(13deg);
    -webkit-transform: skew(7deg) rotate(13deg);
    -ms-transform: skew(7deg) rotate(13deg);
}

.shadowy {
    -moz-box-shadow: 8px 8px 15px rgba(0,0,0,.7);
    -webkit-box-shadow: 8px 8px 15px rgba(0,0,0,.7);
    -ms-box-shadow: 8px 8px 15px rgba(0,0,0,.7);
    box-shadow: 8px 8px 15px rgba(0,0,0,.7);
    -o-box-shadow: 8px 8px 15px rgba(0,0,0,.7);
}

.baseBox a {
    color: #fff;
}