【问题标题】:Callback execution sequence in Javascript, Retrieving from IndexeddBJavascript 中的回调执行序列,从 IndexeddB 中检索
【发布时间】:2023-03-16 08:48:01
【问题描述】:

如果我有这样的代码:

testJSCallbacks();

function testJSCallbacks(){
    var i = 0;

    for (i = 0; i < 5; i++){
        console.log("Step 1 "+i);
        foo(i, myCB);
    }

}

function foo(key, fnCB){
    //retrieve png image blob from indexedDB for the key 'key'. Assume that the database is
    //created and started properly
    var getRequest = transaction.objectStore("store").get(key);
    getRequest.onsuccess = function (event) {
        var result = event.target.result;
        if(result){
            console.log("Step 2 " + key + " Found");
        }else{
            console.log("Step 2 " + key + " not Found");    //for the same 'key' value this happens before the above result is valid. i.e. key found
        }
        fnCB(result);
    }
}

myCB = function (result){
    console.log("Step 3 "+result);
}

实际输出(仅举例):

Step 1 0
Step 1 1
Step 1 2
Step 1 3
Step 1 4

Step 2 0 Found
.
.

Step 3 <result>
.
.
.

期望的输出:

 Step 1 0
 Step 2 0 Found
 Step 3 <result value of key 0 goes here>

在我的代码中,我试图从 IndexedDB 中读取 png blob,这些 blob 已在之前存储。但是在读取/搜索特定 blob 时,需要很长时间才能返回结果,同时即使之前的搜索尚未完成,也会搜索第二个 blob。

如果您需要在循环中多次调用异步函数并且回调需要很长时间才能到达,谁能建议您该怎么做?我的代码是否正确且合乎逻辑,或者这不是 javascript 的完成方式?我对此很陌生,并且来自嵌入式 C 背景。

【问题讨论】:

    标签: javascript callback indexeddb


    【解决方案1】:

    问题是getRequest.onsuccess函数是异步的,而for循环是同步执行的。这就是它首先完成的原因......事实上,当您执行 testJsCallbacks 时,在当前执行上下文结束并将控制权返回给 javascript 事件队列之前不会执行任何其他操作,因为浏览器中的 javascript 执行上下文是单线程的。

    为了做你想做的事,我建议使用promise library。然后你可以写这样的代码(见jsfiddle使用Q.js库):

    testJSCallbacks();
    
    function testJSCallbacks(){
        var i = 0,
            promise;
    
        for (i = 0; i < 5; i++) {
            //Make initial promise if one doesn't exist
            if (!promise) {
                promise = Q.fcall(getStep(i));
            }
            //Append to existing promise chain
            else {
                promise = promise.then(getStep(i));
            }
            //then function returns another promise that can be used for chaining.
            //We are essentially chaining each function together here in the loop.
            promise = promise.then(function (key) {
                //Log the output of step here
                console.log("Step 1 " + key);
                return key;
            })
            //then function takes a callback function with one parammeter (the data).
            //foo signature meets this criteria and will use the resolution of the last promise (key).
            .then(foo)
            //myCB will execute after foo resolves its promise, which it does in the onsuccess callback
            .then(myCB);
        }
    }
    
    function getStep(step) {
        return function () {
            return step;
        }
    }
    
    function foo(key) {
        //retrieve png image blob from indexedDB for the key 'key'. Assume that the database is
        //created and started properly
        var getRequest = transaction.objectStore("store").get(key),
            //Need to return a promise
            deferred = Q.defer();
        getRequest.onsuccess = function (event) {
            var result = event.target.result;
            if(result){
                console.log("Step 2 " + key + " Found");
            }else{
                console.log("Step 2 " + key + " not Found");    //for the same 'key' value this happens before the above result is valid. i.e. key found
            }
            deferred.resolve(result);
        }
    
        return deferred.promise;
    }
    
    function myCB (result){
        console.log("Step 3: " + result);
    }
    

    jsfiddle 使用 setTimeout 代替 objectStore 来展示异步特性。

    解释 getStep 函数

    getStep 函数就像一个“种子”函数,它开始解析您想要执行的操作链(即步骤 1、步骤 2、步骤 3)。它只是创建了一个函数,该函数返回传入的变量的值。这用于传递给 console.logs 承诺解析链中的第 1 步的函数,然后返回下一个承诺解析的值(第 2 步)。 . JavaScript 有闭包的概念,为了获得正确的步数值(而不是执行回调时的 'i' 值),我们需要为变量 i 创建一个闭包。

    为了演示,请考虑以下代码:

    HTML:

    <button type="button">0</button>
    <button type="button">1</button>
    <button type="button">2</button>
    <button type="button">3</button>
    <button type="button">4</button>
    
    addHandlers();
    
    function addHandlers() {
        //Don't do this. Just demonstrating a feature:
        var buttons = document.getElementsByTagName("button") || [],
            i, len = buttons.length;
        for (var i = 0; i < len; i++) {
            buttons[i].onclick = function () {
                //will always alert 5
                alert(i);
            }
        }
    }
    

    由于在 for 循环结束后变量 i 为 5,因此这是函数中使用的值。这就是为什么您需要为 i 创建一个闭包(为清楚起见再次使用 getStep):

    addHandlers();
    
    function addHandlers() {
        var buttons = document.getElementsByTagName("button") || [],
            i, len = buttons.length;
    
    
        for (var i = 0; i < len; i++) {
            //getStep creates a function with a closure for i at its current value in the loop
            buttons[i].onclick = getStep(i);
        }
    }
    
    function getStep(i) {
        return function () {
                alert(i);
            }
    }
    

    beforeafter 的小提琴。

    【讨论】:

    • 谢谢。在 SO 和其他地方搜索之后,我确实了解了 deferred & promise 库。但这对我来说没有多大意义,因为我不知道如何将这些碎片拼凑在一起。您的示例清除了这一点。对我来说有很多新信息,所以必须先消化一下。我会尝试/学习这一点,如果我有问题或疑问,可能会在稍后回复。
    • 在我开始使用它们之前,它们对我没有多大意义。 Async JavaScript (pragprog.com/book/tbajs/async-javascript) 是一本很好的读物。大量信息打包成少量页面(我认为大约 100 页)。这本书确实假设您对 javascript 非常熟悉,但其中详细介绍了 javascript 的执行方式和良好的异步模式。
    • 你能解释一下getStep()函数的作用吗?
    • promise 定义定义了方法采用一个参数。这就是如何定义一个承诺。当你解决(或拒绝)一个承诺时,你会传递一个值。该值用于调用承诺链中的下一个函数。如果你想传递不止一件东西给 myCB,用你想要的一切对象来解决这个承诺。例如。第 2 步中的 deferred.resolve({key: key, result: result})。然后 myCB 中的“result”参数将具有 result.key 和 result.result。
    • 您可以传递任何 JavaScript 值(或根本不传递值,在这种情况下它将是未定义的)。我只建议了一个对象,因为它可以让您定义键/值对,这听起来像是您想在第 3 步/myCB 中使用的东西。
    猜你喜欢
    • 1970-01-01
    • 2011-10-10
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 2019-08-05
    • 2018-03-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多