2015-01-10 20 views
0

假設這個簡單的代碼:如何延遲所有對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); 
    } 
}); 

,因爲當被稱爲木屐和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); 
    } 
}); 

但在我看來,大量的開銷,以每類下面的這個模式被寫入。

有沒有更好的方法來處理這個功能?

是否存在任何庫來簡化此功能?

回答

1

準備好這個問題,來到我的腦海,可能會是一個很好的答案。這個想法是使用工廠功能的概念。上面的代碼將以這種方式重寫。

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

我分享此Q/A,因爲我可以對其他身體有趣。如果你有更好的解決方案,圖書館來處理這種情況,或任何其他想法,我會感激。

+2

我相當懷疑你正在用異步回調中的所有return語句完成任何事情。那些返回值只是回到異步基礎架構,而不是回到原來的調用者。 – jfriend00

+0

同意,我只是刪除了回報。 – jbaylina

0

您可以在MyClass內部使用Promise,並讓所有函數在繼續之前等待承諾完成。

http://api.jquery.com/promise/

+0

您不能從構造函數返回承諾。構造函數必須返回該對象。這是這個問題。 – jfriend00

+0

MyClass可以在內部存儲承諾,並且可以在承諾解決或拒絕時繼續執行所有功能。 – nhylated

+0

爲什麼你不顯示這將如何工作以及其他函數的調用者如何知道其他函數何時完成以及如何傳播和處理初始化中的錯誤。我真的不認爲你應該嘗試隱藏調用者的異步行爲。調用者需要知道什麼時候異步,以便他們可以相應地進行編程。 – jfriend00

1

這裏通常的策略是不使用任何異步操作從構造函數。如果一個對象需要以初始化一個異步操作,那麼您使用的兩個選項之一:

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

就個人而言,我可能會使用一個設計中使用的承諾,以便調用代碼看起來是這樣的:

var obj = new MyClass(...); 
obj.init(db).then(function() { 
    // object successfully initialized, use it here 
}, function(err) { 
    // deal with initialization error here 
}); 
0

您可以將前提條件注入功能中:

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

這就是說,你的工廠方法會更好。