Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Commit

Permalink
Implement promises entirely in JS
Browse files Browse the repository at this point in the history
  • Loading branch information
ry committed Dec 6, 2009
1 parent 3414eab commit 4d818f1
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 203 deletions.
8 changes: 1 addition & 7 deletions lib/sys.js
Expand Up @@ -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;
1 change: 0 additions & 1 deletion src/node.cc
Expand Up @@ -811,7 +811,6 @@ static Local<Object> 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
Expand Down
96 changes: 75 additions & 21 deletions src/node.js
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
};

Expand All @@ -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 {
Expand Down
138 changes: 0 additions & 138 deletions src/node_events.cc
Expand Up @@ -22,10 +22,6 @@ using namespace v8;

Persistent<FunctionTemplate> EventEmitter::constructor_template;

/* Poor Man's coroutines */
static Promise *coroutine_top;
static int coroutine_stack_size;

void EventEmitter::Initialize(Local<FunctionTemplate> ctemplate) {
HandleScope scope;

Expand All @@ -37,9 +33,6 @@ void EventEmitter::Initialize(Local<FunctionTemplate> 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<Object> self,
Expand Down Expand Up @@ -104,135 +97,4 @@ bool EventEmitter::Emit(const char *event_s, int argc, Handle<Value> argv[]) {
return ReallyEmit(handle_, event, argc, argv);
}

Persistent<FunctionTemplate> Promise::constructor_template;

void Promise::Initialize(v8::Handle<v8::Object> target) {
HandleScope scope;

Local<FunctionTemplate> t = FunctionTemplate::New(New);
constructor_template = Persistent<FunctionTemplate>::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<v8::Value> Promise::New(const v8::Arguments& args) {
HandleScope scope;

Promise *promise = new Promise();
promise->Wrap(args.This());
promise->Attach();

return args.This();
}

Handle<Value> Promise::Block(const Arguments& args) {
HandleScope scope;
Promise *promise = ObjectWrap::Unwrap<Promise>(args.Holder());
promise->Block();
return Undefined();
}

v8::Handle<v8::Value> Promise::EmitSuccess(const v8::Arguments& args) {
HandleScope scope;
Promise *promise = ObjectWrap::Unwrap<Promise>(args.Holder());

int argc = args.Length();
Local<Value> 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<v8::Value> Promise::EmitError(const v8::Arguments& args) {
HandleScope scope;
Promise *promise = ObjectWrap::Unwrap<Promise>(args.Holder());

int argc = args.Length();
Local<Value> 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<v8::Value> 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<v8::Value> 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<Object> handle =
Promise::constructor_template->GetFunction()->NewInstance();

return ObjectWrap::Unwrap<Promise>(handle);
}

} // namespace node
36 changes: 0 additions & 36 deletions src/node_events.h
Expand Up @@ -20,41 +20,5 @@ class EventEmitter : public ObjectWrap {
EventEmitter() : ObjectWrap () { }
};

class Promise : public EventEmitter {
public:
static void Initialize(v8::Handle<v8::Object> target);

static v8::Persistent<v8::FunctionTemplate> constructor_template;
static Promise* Create(void);

bool EmitSuccess(int argc, v8::Handle<v8::Value> argv[]);
bool EmitError(int argc, v8::Handle<v8::Value> argv[]);
void Block();

v8::Handle<v8::Object> Handle() {
return handle_;
}

protected:
static v8::Handle<v8::Value> New(const v8::Arguments& args);
static v8::Handle<v8::Value> Block(const v8::Arguments& args);
static v8::Handle<v8::Value> EmitSuccess(const v8::Arguments& args);
static v8::Handle<v8::Value> 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_

0 comments on commit 4d818f1

Please sign in to comment.