I see a lot of synchronous functions in the file system library. such as fs.readFileSync(filename, [options])
.
How (and why) are these functions implemented if node has async/non-blocking IO and no sleep method - and can I use the same mechanism to implement other synchronous functions?
fs.readFileSync()
is really just a wrapper for the
fs.readSync()
function. So the question is how is fs.readSync() implemented compared to fs.read(). If you look at the implementations of these two functions they both take advantage of the bindings module. Which in this case is intialized to
var binding = process.binding('fs').
and the calls are
binding.read(fd, buffer, offset, length, position, wrapper);//async
var r = binding.read(fd, buffer, offset, length, position);//sync
Respectively. Once we're in the "binding" module, we are out in v8, node_#####.cc land. The implementation of binding('fs') can be found in the node repository code, in node_file.cc. The node engine offers overloads for the C++ calls, one taking a callback, one that does not. The node_file.cc code takes advantage of the req_wrap class. This is a wrapper for the v8 engine. In node_file.cc we see this:
#define ASYNC_CALL(func, callback, ...) \
FSReqWrap* req_wrap = new FSReqWrap(#func); \
int r = uv_fs_##func(uv_default_loop(), &req_wrap->req_, \
__VA_ARGS__, After); \
req_wrap->object_->Set(oncomplete_sym, callback); \
req_wrap->Dispatched(); \
if (r < 0) { \
uv_fs_t* req = &req_wrap->req_; \
req->result = r; \
req->path = NULL; \
req->errorno = uv_last_error(uv_default_loop()).code; \
After(req); \
} \
return scope.Close(req_wrap->object_);
#define SYNC_CALL(func, path, ...) \
fs_req_wrap req_wrap; \
int result = uv_fs_##func(uv_default_loop(), &req_wrap.req, __VA_ARGS__, NULL); \
if (result < 0) { \
int code = uv_last_error(uv_default_loop()).code; \
return ThrowException(UVException(code, #func, "", path)); \
}
Notice that the SYNC_CALL uses a different req-wrap. Here is the code for the relevant req_wrap constructor for the ASYNC method, found in req_wrap.h
ReqWrap() {
v8::HandleScope scope;
object_ = v8::Persistent<v8::Object>::New(v8::Object::New());
v8::Local<v8::Value> domain = v8::Context::GetCurrent()
->Global()
->Get(process_symbol)
->ToObject()
->Get(domain_symbol);
if (!domain->IsUndefined()) {
// fprintf(stderr, "setting domain on ReqWrap\n");
object_->Set(domain_symbol, domain);
}
ngx_queue_insert_tail(&req_wrap_queue, &req_wrap_queue_);
}
Notice that this function is creating a new v8 scope object to handle the running of this event. This is where the asynchronous portion of async stuff happens. The v8 engine launches a new javascript interpreting environment to handle this particular call separately. In short, without building/modifying your own version of node, you cannot implement your own asynchronous/synchronous versions of calls, in the same way that node does. That being said, asynchronous really only applies to I/O operations. Perhaps a description of why you think you need things to be more synchronous would be in order. In general, if you believe node doesn't support something you want to do, you just aren't embracing the callbacks mechanism to it's full potential.
That being said, you could consider using the events node module to implement your own event handlers if you need async behavior. And you can consider native extensions if there are things you desperately need to do synchronously, however, I highly recommend against this. Consider how you can work within the asynchronous event loop to get what you need to do done this way. Embrace this style of thinking, or switch to another language.
Forcing a language to handling things a way it doesn't want to handle them is an excellent way to write bad code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With