假设这段简单的代码:
/*jslint node: true */
"use strict";
function MyClass(db) {
var self = this;
this._initError = new Error("MyClass not initialized");
db.loadDataAsyncronously(function(err, data) {
if (err) {
self._initError =err;
} else {
self._initError = null;
self.data = data;
}
});
}
MyClass.prototype.getA = function(cb) {
if (this._initError) {
return cb(this._initError);
}
return cb(null, this.data.a);
};
MyClass.prototype.getB = function(cb) {
if (this._initError) {
return cb(this._initError);
}
return cb(null, this.data.b);
};
var db = {
loadDataAsyncronously: function(cb) {
// Emulate the load process from disk.
// Data will be available later
setTimeout(function() {
cb(null, {
a:"Hello",
b:"World"
});
},1000);
}
};
var obj = new MyClass(db);
obj.getA(function (err, a) {
if (err) {
console.log(err);
} else {
console.log("a: " + a);
}
});
obj.getB(function (err, b) {
if (err) {
console.log(err);
} else {
console.log("a: " + b);
}
});
这会产生错误,因为在调用 getA 和 getB 方法时,obj 未初始化。我希望在对象初始化之前调用的任何方法都自动延迟,直到类完成初始化。
解决它的一种方法是这样的:
/*jslint node: true */
"use strict";
function MyClass(db) {
var self = this;
self._pendingAfterInitCalls = [];
db.loadDataAsyncronously(function(err, data) {
if (!err) {
self.data = data;
}
self._finishInitialization(err);
});
}
MyClass.prototype.getA = function(cb) {
this._waitUntiliInitialized(function(err) {
if (err) {
return cb(err);
}
return cb(null, this.data.a);
});
};
MyClass.prototype.getB = function(cb) {
this._waitUntiliInitialized(function(err) {
if (err) {
return cb(err);
}
return cb(null, this.data.b);
});
};
MyClass.prototype._finishInitialization = function(err) {
this._initialized=true;
if (err) {
this._initError = err;
}
this._pendingAfterInitCalls.forEach(function(call) {
call(err);
});
delete this._pendingAfterInitCalls;
};
MyClass.prototype._waitUntiliInitialized = function(cb) {
var bindedCall = cb.bind(this);
if (this._initialized) {
return bindedCall(this._initError);
}
this._pendingAfterInitCalls.push(bindedCall);
};
var db = {
loadDataAsyncronously: function(cb) {
// Emulate the load process from disk.
// Data will be available later
setTimeout(function() {
cb(null, {
a:"Hello",
b:"World"
});
},1000);
}
};
var obj = new MyClass(db);
obj.getA(function (err, a) {
if (err) {
console.log(err);
} else {
console.log("a: " + a);
}
});
obj.getB(function (err, b) {
if (err) {
console.log(err);
} else {
console.log("a: " + b);
}
});
但在我看来,按照这种模式为每个类编写很多开销。
有没有更优雅的方式来处理此功能?
是否存在任何库来简化此功能?
准备这个问题,我想到可能是一个很好的答案。这个想法是使用工厂函数的概念。上面的代码将以这种方式重写。
/*jslint node: true */
"use strict";
function createMyClass(db, cb) {
var obj = new MyClass();
obj._init(db, function(err) {
if (err) return cb(err);
cb(null, obj);
});
}
function MyClass() {
}
MyClass.prototype._init = function(db, cb) {
var self=this;
db.loadDataAsyncronously(function(err, data) {
if (err) return cb(err);
self.data = data;
cb();
});
};
MyClass.prototype.getA = function(cb) {
if (this._initError) return cb(this._initError);
cb(null, this.data.a);
};
MyClass.prototype.getB = function(cb) {
if (this._initError) return cb(this._initError);
cb(null, this.data.b);
};
var db = {
loadDataAsyncronously: function(cb) {
// Emulate the load process from disk.
// Data will be available later
setTimeout(function() {
cb(null, {
a:"Hello",
b:"World"
});
},1000);
}
};
var obj;
createMyClass(db,function(err, aObj) {
if (err) {
console.log(err);
return;
}
obj=aObj;
obj.getA(function (err, a) {
if (err) {
console.log(err);
} else {
console.log("a: " + a);
}
});
obj.getB(function (err, b) {
if (err) {
console.log(err);
} else {
console.log("a: " + b);
}
});
});
我分享这个问答,因为我觉得它对其他人来说可能很有趣。如果你认为有更好的解决方案,处理这种情况的库,或任何其他想法,我将不胜感激。
这里的常用策略是不使用构造函数中的任何异步操作。 如果对象需要异步操作才能初始化它,则可以使用以下两个选项之一:
-
初始化的异步部分在必须由对象的创建者调用的
.init(cb)
方法中完成。 -
您使用一个工厂函数,该函数接受在操作的异步部分完成时调用的回调(如您建议的答案)。
如果你创建了很多这样的,也许工厂函数是有意义的,因为它可以节省一些重复的代码。 如果你没有创建很多,我更喜欢第一个选项,因为我认为它使代码中确切地了解发生了什么(你创建一个对象,然后异步初始化它,然后代码仅在异步操作完成时继续)。
对于第一个选项,它可能如下所示:
function MyClass(...) {
// initialize instance variables here
}
MyClass.prototype.init = function(db, callback) {
var self = this;
db.loadDataAsyncronously(function(err, data) {
if (err) {
self._initError = err;
} else {
self._initError = null;
self.data = data;
}
callback(err);
});
}
// usage
var obj = new MyClass(...);
obj.init(db, function(err) {
if (!err) {
// continue with rest of the code that uses this object
// in here
} else {
// deal with initialization error here
}
});
就个人而言,我可能会使用使用 promise 的设计,因此调用代码可能如下所示:
var obj = new MyClass(...);
obj.init(db).then(function() {
// object successfully initialized, use it here
}, function(err) {
// deal with initialization error here
});
您可以在 MyClass 内部使用承诺,并让所有函数等待承诺完成,然后再继续。
http://api.jquery.com/promise/
您可以将前提条件注入到函数中:
function injectInitWait(fn) {
return function() {
var args = Array.prototype.slice.call(arguments, 0);
this._waitUntiliInitialized(function(err) {
if (err) return args[args.length - 1](err);
fn.apply(this, args);
});
}
}
injectInitWait(MyClass.prototype.getA);
injectInitWait(MyClass.prototype.getB);
话虽如此,您的工厂方法会更好。