From 4d818f1fd3454f972ef6e0a4f32ed26585804dd5 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 6 Dec 2009 11:36:22 +0100 Subject: [PATCH] Implement promises entirely in JS --- lib/sys.js | 8 +-- src/node.cc | 1 - src/node.js | 96 ++++++++++++++++++++++++------- src/node_events.cc | 138 --------------------------------------------- src/node_events.h | 36 ------------ 5 files changed, 76 insertions(+), 203 deletions(-) diff --git a/lib/sys.js b/lib/sys.js index da749db8dc..f755d71638 100644 --- a/lib/sys.js +++ b/lib/sys.js @@ -81,10 +81,4 @@ exports.exec = function (command) { * prototype * @param {function} superCtor Constructor function to inherit prototype from */ -exports.inherits = function (ctor, superCtor) { - var tempCtor = function(){}; - tempCtor.prototype = superCtor.prototype; - ctor.super_ = superCtor.prototype; - ctor.prototype = new tempCtor(); - ctor.prototype.constructor = ctor; -}; +exports.inherits = process.inherits; diff --git a/src/node.cc b/src/node.cc index cadb552ba6..781171b6d0 100644 --- a/src/node.cc +++ b/src/node.cc @@ -811,7 +811,6 @@ static Local Load(int argc, char *argv[]) { // Initialize the C++ modules..................filename of module - Promise::Initialize(process); // events.cc Stdio::Initialize(process); // stdio.cc Timer::Initialize(process); // timer.cc SignalHandler::Initialize(process); // signal_handler.cc diff --git a/src/node.js b/src/node.js index 1bdd65b5bf..2a52c251c8 100644 --- a/src/node.js +++ b/src/node.js @@ -70,10 +70,6 @@ node.inherits = function () { throw new Error("node.inherits() has moved. Use require('sys') to access it."); }; -process.inherits = function () { - throw new Error("process.inherits() has moved. Use require('sys') to access it."); -}; - process.createChildProcess = function (file, args, env) { var child = new process.ChildProcess(); @@ -184,6 +180,24 @@ process.EventEmitter.prototype.listeners = function (type) { return this._events[type]; }; +process.inherits = function (ctor, superCtor) { + var tempCtor = function(){}; + tempCtor.prototype = superCtor.prototype; + ctor.super_ = superCtor.prototype; + ctor.prototype = new tempCtor(); + ctor.prototype.constructor = ctor; +}; + + +// Promise + +process.Promise = function () { + process.EventEmitter.call(); + this._blocking = false; + this._hasFired = false; +} +process.inherits(process.Promise, process.EventEmitter); + process.Promise.prototype.timeout = function(timeout) { if (timeout === undefined) { return this._timeoutDuration; @@ -235,7 +249,22 @@ process.Promise.prototype.cancel = function() { process.Promise.prototype.emitCancel = function() { var args = Array.prototype.slice.call(arguments); args.unshift('cancel'); + this.emit.apply(this, args); +}; + +process.Promise.prototype.emitSuccess = function() { + if (this.hasFired) return; + var args = Array.prototype.slice.call(arguments); + args.unshift('success'); + this.hasFired = true; + this.emit.apply(this, args); +}; +process.Promise.prototype.emitError = function() { + if (this.hasFired) return; + var args = Array.prototype.slice.call(arguments); + args.unshift('error'); + this.hasFired = true; this.emit.apply(this, args); }; @@ -254,26 +283,51 @@ process.Promise.prototype.addCancelback = function (listener) { return this; }; +/* Poor Man's coroutines */ +var coroutineStack = []; + +process.Promise.prototype._destack = function () { + this._blocking = false; + + while (coroutineStack.length > 0 && + !coroutineStack[coroutineStack.length-1]._blocking) + { + coroutineStack.pop(); + process.unloop("one"); + } +}; + process.Promise.prototype.wait = function () { + var self = this; var ret; - var had_error = false; - this.addCallback(function () { - if (arguments.length == 1) { - ret = arguments[0]; - } else if (arguments.length > 1) { - ret = []; - for (var i = 0; i < arguments.length; i++) { - ret.push(arguments[i]); - } - } - }) - .addErrback(function (arg) { - had_error = true; - ret = arg; - }) - .block(); + var hadError = false; + + self.addCallback(function () { + if (arguments.length == 1) { + ret = arguments[0]; + } else if (arguments.length > 1) { + ret = Array.prototype.slice.call(arguments); + } + self._destack(); + }); + + self.addErrback(function (arg) { + hadError = true; + ret = arg; + self._destack(); + }); + + coroutineStack.push(self); + if (coroutineStack.length > 10) { + process.stdio.writeError("WARNING: promise.wait() is being called too often.\n"); + } + self._blocking = true; + + process.loop(); + + process.assert(self._blocking == false); - if (had_error) { + if (hadError) { if (ret) { throw ret; } else { diff --git a/src/node_events.cc b/src/node_events.cc index 6e0d5de3f9..215432374a 100644 --- a/src/node_events.cc +++ b/src/node_events.cc @@ -22,10 +22,6 @@ using namespace v8; Persistent EventEmitter::constructor_template; -/* Poor Man's coroutines */ -static Promise *coroutine_top; -static int coroutine_stack_size; - void EventEmitter::Initialize(Local ctemplate) { HandleScope scope; @@ -37,9 +33,6 @@ void EventEmitter::Initialize(Local ctemplate) { constructor_template->SetClassName(String::NewSymbol("EventEmitter")); // All other prototype methods are defined in events.js - - coroutine_top = NULL; - coroutine_stack_size = 0; } static bool ReallyEmit(Handle self, @@ -104,135 +97,4 @@ bool EventEmitter::Emit(const char *event_s, int argc, Handle argv[]) { return ReallyEmit(handle_, event, argc, argv); } -Persistent Promise::constructor_template; - -void Promise::Initialize(v8::Handle target) { - HandleScope scope; - - Local t = FunctionTemplate::New(New); - constructor_template = Persistent::New(t); - constructor_template->Inherit(EventEmitter::constructor_template); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - constructor_template->SetClassName(String::NewSymbol("Promise")); - - NODE_SET_PROTOTYPE_METHOD(constructor_template, "block", Block); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "emitSuccess", EmitSuccess); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "emitError", EmitError); - - target->Set(String::NewSymbol("Promise"), - constructor_template->GetFunction()); -} - -v8::Handle Promise::New(const v8::Arguments& args) { - HandleScope scope; - - Promise *promise = new Promise(); - promise->Wrap(args.This()); - promise->Attach(); - - return args.This(); -} - -Handle Promise::Block(const Arguments& args) { - HandleScope scope; - Promise *promise = ObjectWrap::Unwrap(args.Holder()); - promise->Block(); - return Undefined(); -} - -v8::Handle Promise::EmitSuccess(const v8::Arguments& args) { - HandleScope scope; - Promise *promise = ObjectWrap::Unwrap(args.Holder()); - - int argc = args.Length(); - Local argv[argc]; - for (int i = 0; i < argc; i++) { - argv[i] = args[i]; - } - - bool r = promise->EmitSuccess(argc, argv); - - return r ? True() : False(); -} - -v8::Handle Promise::EmitError(const v8::Arguments& args) { - HandleScope scope; - Promise *promise = ObjectWrap::Unwrap(args.Holder()); - - int argc = args.Length(); - Local argv[argc]; - for (int i = 0; i < argc; i++) { - argv[i] = args[i]; - } - - bool r = promise->EmitError(argc, argv); - - return r ? True() : False(); -} - -void Promise::Block(void) { - blocking_ = true; - - assert(prev_ == NULL); - if (coroutine_top) prev_ = coroutine_top; - coroutine_top = this; - coroutine_stack_size++; - if (coroutine_stack_size > 10) { - fprintf(stderr, "(node) WARNING: promise.wait() is being called too often.\n"); - } - - ev_loop(EV_DEFAULT_UC_ 0); - - assert(!blocking_); -} - -void Promise::Destack() { - assert(coroutine_top == this); - ev_unloop(EV_DEFAULT_ EVUNLOOP_ONE); - coroutine_top = prev_; - prev_ = NULL; - coroutine_stack_size--; -} - -void Promise::Detach(void) { - /* Poor Man's coroutines */ - blocking_ = false; - while (coroutine_top && !coroutine_top->blocking_) { - coroutine_top->Destack(); - } - - ObjectWrap::Detach(); -} - -bool Promise::EmitSuccess(int argc, v8::Handle argv[]) { - if (has_fired_) return false; - - bool r = Emit("success", argc, argv); - - has_fired_ = true; - Detach(); - - return r; -} - -bool Promise::EmitError(int argc, v8::Handle argv[]) { - if (has_fired_) return false; - - bool r = Emit("error", argc, argv); - - has_fired_ = true; - Detach(); - - return r; -} - -Promise* Promise::Create(void) { - HandleScope scope; - - Local handle = - Promise::constructor_template->GetFunction()->NewInstance(); - - return ObjectWrap::Unwrap(handle); -} - } // namespace node diff --git a/src/node_events.h b/src/node_events.h index 23433a543f..9c613bbde0 100644 --- a/src/node_events.h +++ b/src/node_events.h @@ -20,41 +20,5 @@ class EventEmitter : public ObjectWrap { EventEmitter() : ObjectWrap () { } }; -class Promise : public EventEmitter { - public: - static void Initialize(v8::Handle target); - - static v8::Persistent constructor_template; - static Promise* Create(void); - - bool EmitSuccess(int argc, v8::Handle argv[]); - bool EmitError(int argc, v8::Handle argv[]); - void Block(); - - v8::Handle Handle() { - return handle_; - } - - protected: - static v8::Handle New(const v8::Arguments& args); - static v8::Handle Block(const v8::Arguments& args); - static v8::Handle EmitSuccess(const v8::Arguments& args); - static v8::Handle EmitError(const v8::Arguments& args); - - virtual void Detach(void); - - bool has_fired_; - bool blocking_; - Promise *prev_; /* for the prev in the Poor Man's coroutine stack */ - - void Destack(); - - Promise() : EventEmitter() { - has_fired_ = false; - blocking_ = false; - prev_ = NULL; - } -}; - } // namespace node #endif // SRC_EVENTS_H_