如何延迟对 javascript 对象的任何方法的所有调用,直到对象初始化



假设这段简单的代码:

/*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);
        }
    });
});

我分享这个问答,因为我觉得它对其他人来说可能很有趣。如果你认为有更好的解决方案,处理这种情况的库,或任何其他想法,我将不胜感激。

这里的常用策略是不使用构造函数中的任何异步操作。 如果对象需要异步操作才能初始化它,则可以使用以下两个选项之一:

  1. 初始化的异步部分在必须由对象的创建者调用的.init(cb)方法中完成。

  2. 您使用一个工厂函数,该函数接受在操作的异步部分完成时调用的回调(如您建议的答案)。

如果你创建了很多这样的,也许工厂函数是有意义的,因为它可以节省一些重复的代码。 如果你没有创建很多,我更喜欢第一个选项,因为我认为它使代码中确切地了解发生了什么(你创建一个对象,然后异步初始化它,然后代码仅在异步操作完成时继续)。

对于第一个选项,它可能如下所示:

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);

话虽如此,您的工厂方法会更好。

最新更新