我一直在寻找有关为网络存储设置IndexedDB的帮助,我遇到了一个我无法找到合适答案的问题。成功设置/打开数据库后,我无法传递包含数据库信息的变量以便以后访问它。这是我的代码,我一直在关注MDN的指南:
const DB_NAME = 'database-name';
const DB_VERSION = 2;
const DB_STORE_NAME = 'users';
var db;
function openDb() {
console.log('openDb ...')
var request = indexedDB.open(DB_NAME, DB_VERSION);
request.onsuccess = function(event) {
db = request.result;
console.log("openDb DONE");
};
request.onerror = function(event) {
console.log(request.errorCode);
};
request.onupgradeneeded = function(event) {
console.log('openDb.onupgradeneeded');
var store = event.currentTarget.result.createObjectStore(DB_STORE_NAME, { keyPath: 'id', autoIncrement: true });
store.createIndex('age', 'age', { unique: false });
};
}
function getObjectStore(store_name, mode) {
var tx = db.transaction(store_name, mode);
return tx.objectStore(store_name);
}
调用getObjectStore
时,变量db
未定义。我对javascript的了解非常有限,而且还有一些我无法理解的概念。该指南并没有显示任何特殊的内容,他们的演示按原样运行。其他一些指南提到实施回调,但他们没有表明它是如何完成的,也不了解回调的概念。任何帮助表示赞赏。感谢。
答案 0 :(得分:4)
不幸的是,在继续使用indexedDB之前,您需要了解一个相对复杂的概念,通常称为异步JavaScript。 stackoverflow上已有几千个与AJAX相关的问题。我想用最礼貌的方式来说这个,但基本上,你正在寻找的答案已经由其他问题以及许多其他网站提供。不过,这里有一些快速提示。
首先,你的方法永远不会奏效。你不能跳过学习异步的知识。
其次,不要使用setTimeout技巧让它工作。这是一个可怕的建议。
第三,在一般情况下,回调只是用于在以特定方式使用函数时描述函数的单词。具体来说,回调是指作为参数传递给另一个函数的函数,其中另一个函数可能在稍后的某个时间点调用该函数。更具体地说,回调函数通常是在函数完成时传递给它的函数末尾调用的函数。
例如:
function a(b) { alert(b); }
function c(d) { d('hi'); }
c(a);
起初看起来有点令人困惑,但这是我能描述的最简单的事情。在该示例中,最后一行调用函数c并传入函数a。代码的效果是你看到' hi'作为浏览器警报。在此示例中,函数a作为参数/参数传递给函数c。函数c使用名称d作为其唯一参数。 c用字符串' hi'来调用d。在描述这个例子时,我们会说参数d代表传递给函数c的回调函数。我们还可以说函数a是函数c使用的特定回调函数。所以基本上就是这样。当您将函数作为参数传递而另一个函数调用传入的参数时,您正在使用回调。
然后事情变得更复杂,因为你必须学习如何读写异步代码。正确地介绍它需要几页。这是一个极端的速成课程。 您传统上一直在编写同步代码,即使您没有这样称呼它。同步代码按照您预期的顺序运行,按照您编写语句的顺序运行。以下是典型同步代码的简要示例:
function sum(a, b) { return a + b; }
alert(sum(1, 2));
简单的东西。下一个示例是使用回调的代码,但仍然是同步的。
function doOperation(op, num1, num2) { return op(num1, num2); }
function sumOperation(num1, num2) { return num1 + num2; }
var result = doOperation(sumOperation, 1, 2);
alert(result);
这里我们将sumOperation函数传递给doOperation函数。 sumOperation是回调函数。这是名为' op'的第一个参数。还是很简单的东西。现在考虑下一个例子。下一个示例的目的是展示我们如何将控制传递给函数来执行某些操作。有点像goto / labels如何工作。
function doOperation(op, num1, num2) {
var result = op(num1, num2);
alert(result);
return undefined;
}
function sumOperation(num1, num2) { return num1 + num2; }
doOperation(sumOperation, 1, 2);
请注意doOperation如何不再返回值。它在其函数体内具有逻辑。因此,一旦我们调用doOp,浏览器就开始在doOperation中运行代码。所以我们从外部上下文切换到函数体。另外,因为doOperation没有返回任何内容,所以我们无法对其返回值做任何事情。逻辑被锁定在doOperation函数的主体内。代码仍然大致相同,只是现在我们没有从doOperation返回任何内容,现在逻辑在doOperation内部而不是在main / global上下文中。
现在使用setTimeout的示例。这与使用setTimeout的建议完全无关。
function doOperation(op, num1, num2) {
setTimeout(function runLater() {
var result = op(num1, num2);
alert(result);
return undefined;
}, 1000);
return undefined;
}
function sumOperation(num1, num2) { return num1 + num2; }
doOperation(sumOperation, 1, 2);
这里的要点是要理解我们使用回调(在此示例中名为runLater),并且回调中的代码不会立即运行。因此,我们不能再说它同步运行。我们将构成回调函数体的语句称为异步。所以现在1秒后出现警报。请注意我们如何从runLater返回任何内容。还要注意我们如何从doOperation中返回任何内容。没有什么可以回报的。没有办法在'结果中获得价值。变量超出了runLater的范围。它被锁定在那里。
让我们尝试几乎相同的事情,但尝试让runLater设置一个变量。此外,我将省略'返回undefined'因为这就是没有显式return语句的每个函数返回的内容。
var aGlobalResult = null;
function doOperation(op, num1, num2) {
setTimeout(function runLater() {
aGlobalResult = op(num1, num2);
}, 1000);
}
function sumOperation(num1, num2) { return num1 + num2; }
doOperation(sumOperation, 1, 2);
alert(aGlobalResult);
希望你能抓住这个问题。首先,runLater不会返回任何内容,因此doOperation不返回任何内容,因此我们甚至无法尝试执行aGlobalResult = doOperation(...);
之类的操作,因为这没有任何意义。其次,这里的结果是你会看到一个警告' undefined'因为alert语句在为aGlobalResult赋值的语句之前执行。即使您在代码中更高(更早)编写了赋值语句,并且警报稍后也是如此。这是一些新开发人员在这里遇到的砖墙。这对某些人来说确实令人困惑。这里未定义aGlobalResult,因为setTimeout直到稍后才设置它。即使我们在0毫秒内传递给setTimeout,它仍然是'之后',这意味着分配发生在警报之后的稍后时间点。警报消息始终未定义。你无能为力,以避免这种方式的运作方式。没有。期。别再试了。学习它,或完全放弃。
那么,人们通常如何编写行为或涉及异步内容的代码?通过使用回调。这再次意味着您不能再使用return语句为外部作用域变量赋值。你想要编写函数并将控制传递给各种函数。换句话说,而不是:
function a() {}
function b() {}
function c() {}
a(); b(); c();
你编写这样的代码:
function a(callback) {
var asdf = 1+2; // do some stuff in a
alert('a finished');
// a has now completed, call its callback function, appropriately named callback
callback();
}
function b(callback) {
var asdfasdfasdf = 3 + 4;
alert('b finished');
// call the callback
callback();
}
a( function(){ b(function() { alert('both a and b finished'); }); });
这更正式地称为continuation passing style或CPS。
所以,这是编写回调函数和基本异步代码的基础知识的一个例子。现在您可以开始使用indexedDB。您将注意到的第一件事是函数indexedDB.open被记录为异步。那么,我们如何使用它呢?像这样:
var someGlobalVariable = null;
var openRequest = indexedDB.open(...);
openRequest.onsuccess = function openRequestOnSuccessCallbackFunction(event) {
// Get a reference to the database variable. If this function is ever called at some later
// point in time, the database variable is now defined as the 'result' property of the
// open request. There are multiple ways to access the result property. Any of the following
// lines works 100% the same way. Use whatever you prefer. It does not matter.
var databaseConnection = openRequest.result;
var databaseConnection = this.result;
var databaseConnection = event.target.result;
var databaseConnection = event.currentTarget.result;
// Now that we have a valid and defined databaseConnection variable, do something with it
// e.g.:
var transaction = databaseConnection.transaction(...);
var objectStore = transaction.objectStore(...);
// etc.
// DO NOT DO THE FOLLOWING, it does not work. Why? Review the early descriptions. First off
// this onsuccess callback function does not return anything. Second off this function gets
// called at some *later* point in time, who knows when. It could be a nanosecond later.
someGlobalVariable = databaseConnection;
};
希望这会让你走上正轨。
编辑:我想我会添加一些介绍。您需要了解的一个相关概念,即我没有对控制进行足够清晰的解释,这是命令式和声明性编程之间的区别。
命令式编程涉及按您编写的顺序执行一系列语句。 您是来电者,并且处于控制之中。命令式代码看起来像这样(虚构代码):
var dbConn = dbFactory.newConnection('mydb');
var tx = dbConn.newTransaction();
var resultCode = tx.put(myObject);
if(resultCode == dbResultConstants.ERROR_PUT_KEY_EXISTS) {
alert('uhoh');
}
声明性编程略有不同。使用声明方法,编写函数,然后将注册(也称为钩子或绑定)函数添加到JavaScript引擎,然后在某些时候,在适当的时候,引擎运行代码。 引擎是调用方并且处于控制之中,而不是您。声明性编程涉及回调,看起来像这样(虚构代码):
dbFactory.newConnection(function onConnect(dbConn) {
dbConn.newTransaction(function onNewTransaction(tx) {
tx.put(myObject, function onPut(resultCode) {
if(resultCode == dbResultConstants.ERROR_PUT_KEY_EXISTS) {
alert('uhoh');
}
});
});
});
在这个例子中,你调用的唯一东西是虚构的dbFactory.newConnection函数。你传递了一个回调函数。您没有自己调用回调函数。引擎调用回调函数。你不能自己调用回调函数。这就是为什么JavaScript引擎可以允许您编写异步代码的全部想法。因为你无法控制语句/功能的执行顺序。引擎可以控制它。你所要做的就是编写你的函数,注册它们,然后启动一系列回调(唯一的命令行,起始语句)。
这就是为什么你问题中的getObjectStore
这样的函数不起作用的原因。你试图自己调用这个函数,但这是倒退的。你只能编写一个函数并注册它(以某种方式将它作为一个回调挂钩),然后引擎,而不是你,稍后调用它。
希望这不会让人感到困惑,但如果您真的想通过将数据库变量作为第一个参数传递给函数,您实际上可以编写函数getObjectStore
。这导致了逻辑上的下一个问题,即如何获得有效的数据库变量以传递给函数。你无法在全球范围内(可靠地)获得一个。因为连接变量仅在onOpen回调函数的上下文中有效。所以你必须在onOpen函数中调用这个函数。类似的东西:
function getObjectStore(db, name, mode) {
var tx = db.transaction(name, mode);
var store = tx.objectStore(name);
return store;
}
var openRequest = indexedDB.open(...);
openRequest.onsuccess = function onOpen(event) {
// get the connection variable. it is defined within this (onOpen) function and open.
var db = this.result;
// call our simple imperative helper function to get the users store. only call it from
// within this onOpen function because that is the only place we can get the 'db' variable.
var usersStore = getObjectStore(db, 'users', 'readwrite');
// do something here with usersStore, inside this function only.
};