我重写了这个问题,因为旧版本显然具有误导性。 请阅读文本并确保您了解我的要求。如果 在黑暗中还有什么东西我会修改这个问题以保持清晰。 请告诉我。
我的一个项目是将库从Python移植到JavaScript。 在I / O方面,Python库完全是阻塞/同步的 等等。对于Python代码来说,这当然是完全正常的。
我计划将同步/阻止方法移植到JavaScript 。 这有几个原因,是否值得付出努力 良好但不同问题。
另外我不想添加异步/非阻塞api。
将其想象为节点中的fs模块,即fs.open
和。{
fs.openSync
共存。
该库是纯JavaScript,将在Node和浏览器中运行。
我认为只在一个地方发生同样的事情才是好事。 因此,可以优选实现可以共享实现的某些部分的方法。 当然没有任何代价,这就是我要问的原因。
我在这里有一个方法的提议,但我打算将其发布为 可能的答案。但是,我正在等待一些认真的讨论 在我决定接受什么作为答案之前。
答案 0 :(得分:3)
如果你在node.js中讨论I / O,那么大多数I / O方法都有同步版本。
没有从异步性到同步性的直接转换。我可以想到两种方法:
为了说明我会假设选项2是更好的选择。以下示例使用Q promises(随npm install q
轻松安装。
承诺背后的想法是,尽管它们是异步的,但返回对象是一个值的 promise ,就像它是一个普通函数一样。
// Normal function
function foo(input) {
return "output";
}
// With promises
function promisedFoo(input) {
// Does stuff asynchronously
return promise;
}
第一个函数接受输入并返回结果。第二个示例接受一个输入并立即返回一个promise,当async任务完成时,该promise将最终解析为一个值。然后,您按如下方式管理此承诺:
var promised_value = promisedFoo(input);
promised_value.then(function(value) {
// Yeah, we now have a value!
})
.fail(function(reason) {
// Oh nos.. something went wrong. It passed in a reason
});
使用承诺,你不再需要担心什么时候会发生。您可以轻松地链接承诺,以便事情同步发生,而无需疯狂的嵌套回调或100个命名函数。
非常值得学习。请记住,承诺旨在使异步代码的行为与同步代码相似,即使它不是阻塞。
答案 1 :(得分:0)
我实现了一个库,可以执行我要求的ObtainJS。
(是的,图书馆使用Promises但不像其他人在这里提出的那样)
重新发布Readme.md:
ObtainJS是一个将异步和异步结合在一起的微框架 同步JavaScript代码。它可以帮助你不要重复自己 (DRY)如果您正在开发具有两者接口的库 阻塞/同步和非阻塞/异步执行模型。
您无需学习 很多。通常,使用ObtainJS定义的函数具有第一个参数 开关,允许您选择执行路径,然后是正常 参数:
// readFile has an obtainJS API:
function readFile(obtainAsyncExecutionSwitch, path) { /* ... */ }
如果obtainSwitch是一个假值,readFile将同步执行 并直接返回结果。
var asyncExecution = false, result;
try {
result = readFile(asyncExecution, './file-to-read.js');
} catch(error) {
// handle the error
}
// do something with result
如果obtainSwitch是一个truthy值,readFile将异步执行 并且总是返回一个承诺。
var asyncExecution = true, promise;
promise = readFile(asyncExecution, './file-to-read.js');
promise.then(
function(result) {
// do something with result
},
function(error){
// handle the error
}
)
// Alternatively, use the returned promise directly:
readFile(asyncExecution, './file-to-read.js')
.then(
function(result) {
// do something with result
},
function(error){
// handle the error
}
)
您也可以使用基于回调的api。请注意,无论如何都会返回Promise。
var asyncExecution;
function unifiedCallback(error, result){
if(error)
// handle the error
else
// do something with result
}
asyncExecution = {unified: unifiedCallback}
readfile(asyncExecution, './file-to-read.js');
或使用单独的回调和错误回复
var asyncExecution;
function callback(result) {
// do something with result
}
function errback(error) {
// handle the error
}
var asyncExecution = {callback: callback, errback: errback}
readfile(asyncExecution, './file-to-read.js');
```
谁将使用ObtainJS实现API,工作更多一点。 和我在一起。
通过定义双重依赖树来实现上述行为:一 用于同步执行路径的操作和用于操作的操作 异步执行路径。
动作是依赖于其他结果的小函数 动作。异步执行路径将回退到同步 如果没有为依赖项定义异步操作,则执行操作。 如果同步,则不会定义异步操作 等效的是非阻塞。这是 DRY !
的地方所以,你所做的就是分裂你的同步和阻塞 小功能中的方法。这些帆船取决于每个的结果 其他。然后为每个定义一个非阻塞的AND异步垃圾 同步和阻塞垃圾。其余的确为你获得了.JS。即:
从上面直接从工作代码处获取 ufoJS
define(['ufojs/obtainJS/lib/obtain'], function(obtain) {
// obtain.factory creates our final function
var readFile = obtain.factory(
// this is the synchronous dependency definition
{
// this action is NOT in the async tree, the async execution
// path will fall back to this method
uri: ['path', function _path2uri(path) {
return path.split('/').map(encodeURIComponent).join('/')
}]
// synchronous AJAX request
, readFile:['uri', function(path) {
var request = new XMLHttpRequest();
request.open('GET', path, false);
request.send(null);
if(request.status !== 200)
throw _errorFromRequest(request);
return request.responseText;
}]
}
,
// this is the asynchronous dependency definition
{
// aynchronous AJAX request
readFile:['uri', '_callback', function(path, callback) {
var request = new XMLHttpRequest()
, result
, error
;
request.open('GET', path, true);
request.onreadystatechange = function (aEvt) {
if (request.readyState != 4 /*DONE*/)
return;
if (request.status !== 200)
error = _errorFromRequest(request);
else
result = request.responseText
callback(error, result)
}
request.send(null);
}]
}
// this are the "regular" function arguments
, ['path']
// this is the "job", a driver function that receives as first
// argument the obtain api. A method that the name of an action or
// of an argument as input and returns its result
// Note that job is potentially called multiple times during
// asynchronoys execution
, function(obtain, path){ return obtain('readFile'); }
);
})
var myFunction = obtain.factory(
// sync actions
{},
// async actions
{},
// arguments
[],
//job
function(obtain){}
);
// To define a getter we give it a name provide a definition array.
{
// sync
sum: ['arg1', 'arg2',
// the last item in the definition array is always the action/getter itself.
// it is called when all dependencies are resolved
function(arg1, arg2) {
// function body.
var value = arg1 + arg2
return value
}]
}
// For asynchronous getters you have different options:
{
// async
// the special name "_callback" will inject a callback function
sample1: ['arg1', '_callback', function(arg1, callback) {
// callback(error, result)
}],
// you can order separate callback and errback when using both special
// names "_callback" and "_errback"
sample2: ['arg1', '_callback', '_errback', function(arg1, callback, errback) {
// errback(error)
// callback(result)
}],
// return a promise
sample3: ['arg1', function(arg1) {
var promise = new Promise(/* do what you have to*/);
return promise
}]
}
操作之前定义数组中的项是依赖项 他们的价值观将被注入到行动呼吁中 可用。
如果依赖项的类型不是字符串:它将作为值注入 直。通过这种方式,您可以有效地进行currying。
如果值的类型是字符串:它会在依赖项中查找 当前执行路径的树(同步或异步)。
如果您希望将String作为值传递给getter,则必须将其定义为
获取的实例.Argument:new obtain.Argument('mystring argument is not a getter')
请注意:obtainJS知道主机对象并传播this
正确地对待所有行动。
/**
* Read the glif from I/O and cache it. Return a reference to the
* cache object: [text, mtime, glifDocument(if alredy build by this.getGLIFDocument)]
*
* Has the obtainJS sync/async api.
*/
GlypSet.prototype._getGLIFcache = obtain.factory(
{ //sync
fileName: ['glyphName', function fileName(glyphName) {
var name = this.contents[glyphName];
if(!(glyphName in this.contents) || this.contents[glyphName] === undefined)
throw new KeyError(glyphName);
return this.contents[glyphName]
}]
, glyphNameInCache: ['glyphName', function(glyphName) {
return glyphName in this._glifCache;
}]
, path: ['fileName', function(fileName) {
return [this.dirName, fileName].join('/');
}]
, mtime: ['path', 'glyphName', function(path, glyphName) {
try {
return this._io.getMtime(false, path);
}
catch(error) {
if(error instanceof IONoEntryError)
error = new KeyError(glyphName, error.stack);
throw error;
}
}]
, text: ['path', 'glyphName', function(path, glyphName) {
try {
return this._io.readFile(false, path);
}
catch(error) {
if(error instanceof IONoEntryError)
error = new KeyError(glyphName, error.stack);
throw error;
}
}]
, refreshedCache: ['glyphName', 'text', 'mtime',
function(glyphName, text, mtime) {
return (this._glifCache[glyphName] = [text, mtime]);
}]
}
//async getters
, {
mtime: ['path', 'glyphName', '_callback',
function(path, glyphName, callback) {
var _callback = function(error, result){
if(error instanceof IONoEntryError)
error = new KeyError(glyphName, error.stack);
callback(error, result)
}
this._io.getMtime({unified: _callback}, path);
}]
, text: ['path', 'glyphName', '_callback',
function(path, glyphName, callback){
var _callback = function(error, result) {
if(error instanceof IONoEntryError)
error = new KeyError(glyphName, error.stack);
callback(error, result)
}
this._io.readFile({unified: _callback}, path);
}
]
}
, ['glyphName']
, function job(obtain, glyphName) {
if(obtain('glyphNameInCache')) {
if(obtain('mtime').getTime() === this._glifCache[glyphName][1].getTime()) {
// cache is fresh
return this._glifCache[glyphName];
}
}
// still here? need read!
// refreshing the cache:
obtain('refreshedCache')
return this._glifCache[glyphName];
}
)
答案 2 :(得分:0)
使用带有async / sync标志的promises编写低级API。
更高级别的异步API直接返回这些承诺(同时也使用像1970年那样的异步回调)。
更高级别的同步API从promise同步解包值并返回值或抛出错误。
(示例使用bluebird,与Q相比,速度提高了几个数量级,并且以文件大小为代价具有更多功能,尽管这可能不适合浏览器。)
未曝光的低级别api:
//lowLevelOp calculates 1+1 and returns the result
//There is a 20% chance of throwing an error
LowLevelClass.prototype.lowLevelOp = function(async, arg1, arg2) {
return new Promise(function(resolve, reject) {
if (Math.random() < 0.2) {
throw new Error("random error");
}
if (!async) resolve(1+1);
else {
//Async
setTimeout(function(){
resolve(1+1);
}, 50);
}
});
};
使用promises或callbacks同步工作的高级公开API:
HighLevelClass.prototype.opSync = function(arg1, arg2) {
var inspection =
this.lowLevel.lowLevelOp(false, arg1, arg2).inspect();
if (inspection.isFulfilled()) {
return inspection.value();
}
else {
throw inspection.error();
}
};
HighLevelClass.prototype.opAsync = function(arg1, arg2, callback) {
//returns a promise as well as accepts callback.
return this.lowLevel.lowLevelOp(true, arg1, arg2).nodeify(callback);
};
您可以自动为同步方法生成高级API:
var LowLevelProto = LowLevelClass.prototype;
Object.keys(LowLevelProto).filter(function(v) {
return typeof LowLevelProto[v] === "function";
}).forEach(function(methodName) {
//If perf is at all a concern you really must do this with a
//new Function instead of closure and reflection
var method = function() {
var inspection = this.lowLevel[methodName].apply(this.lowLevel, arguments);
if (inspection.isFulfilled()) {
return inspection.value();
}
else {
throw inspection.error();
}
};
HighLevelClass.prototype[methodName + "Sync" ] = method;
});