2015-03-03 70 views
0

我有一些代碼,將動態生成一個AJAX請求基於我通過AJAX請求檢索服務器的場景。單功能的多個承諾鏈

的想法是:

  1. 服務器提供了一個「方案」對我來說,生成一個Ajax請求。
  2. 我生成一個基於場景的AJAX請求。
  3. 然後我重複這個過程,一遍又一遍地循環。然而http://jsfiddle.net/3Lddzp9j/11/

    ,我試圖修改上面的代碼,這樣我就可以處理從最初的AJAX請求場景的數組:

我在這裏承諾這樣做。

IE:

{ 
"base": { 
    "frequency": "5000" 
}, 
"endpoints": [ 
    { 
     "method": "GET", 
     "type": "JSON", 
     "endPoint": "https://api.github.com/users/alvarengarichard", 
     "queryParams": { 
      "objectives": "objective1, objective2, objective3" 
     } 
    }, 
    { 
     "method": "GET", 
     "type": "JSON", 
     "endPoint": "https://api.github.com/users/dkang", 
     "queryParams": { 
      "objectives": "objective1, objective2, objective3" 
     } 
    } 
] 

這似乎將是直線前進,但問題似乎是在「waitForTimeout」功能。

我無法弄清楚如何運行多個承諾鏈。我在「延遲」變量中有一系列的承諾,但鏈只在第一個中繼續 - 儘管處於for循環中。

任何人都可以提供見解,爲什麼這是?你可以看到這裏發生了什麼:http://jsfiddle.net/3Lddzp9j/10/

+1

經典for循環沒有閉合問題。在異步代碼完成之前,'i'會增加很長的時間。此外,$ .ajax本身返回一個承諾,所以不需要用'$ .Deferred' – charlietfl 2015-03-03 00:30:20

+0

創建它們。不知道我明白這一點。爲什麼增量會在異步代碼完成之前發生,因爲我希望一次執行多個AJAX調用。基本上,您將繼續爲每個單獨的場景....或延遲數組的每個部分.then()鏈。 – richie 2015-03-03 01:17:56

+1

http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example – charlietfl 2015-03-03 01:20:49

回答

1

的主要問題是:

  • waitForTimeout沒有傳遞所有指令
  • 即使waitForTimeout已修復,那麼callApi不會寫入以執行多個ajax調用。

還有一些代碼的其他問題。

  • 您確實需要一些數據檢查(和相關的錯誤處理)以確保data中存在預期的組件。
  • mapToInstruction是一個不必要的步驟 - 您可以直接從data映射到ajax選項 - 不需要進行中間數據轉換。
  • waitForTimeout可以大大簡化爲單個承諾,通過一次超時解決。
  • 承諾鏈中的同步函數不需要返回承諾 - 它們可以返回結果或未定義。

全部通過使用jQuery堅持,你應該結束了,像這樣:

var App = (function ($) { 
    // Gets the scenario from the API 
    // sugar for $.ajax with GET as method - NOTE: this returns a promise 
    var getScenario = function() { 
     console.log('Getting scenario ...'); 
     return $.get('http://demo3858327.mockable.io/scenario2'); 
    }; 

    var checkData = function (data) { 
     if(!data.endpoints || !data.endpoints.length) { 
      return $.Deferred().reject('no endpoints').promise(); 
     } 
     data.base = data.base || {}; 
     data.base.frequency = data.base.frequency || 1000;//default value 
    }; 

    var waitForTimeout = function(data) { 
     return $.Deferred(function(dfrd) { 
      setTimeout(function() { 
       dfrd.resolve(data.endpoints); 
      }, data.base.frequency); 
     }).promise(); 
    }; 

    var callApi = function(endpoints) { 
     console.log('Calling API with given instructions ...'); 
     return $.when.apply(null, endpoints.map(ep) { 
      return $.ajax({ 
       type: ep.method, 
       dataType: ep.type, 
       url: ep.endpoint 
      }).then(null, function(jqXHR, textStatus, errorThrown) { 
       return textStatus; 
      }); 
     }).then(function() { 
      //convert arguments to an array of results 
      return $.map(arguments, function(arg) { 
       return arg[0]; 
      }); 
     }); 
    }; 

    var handleResults = function(results) { 
     // results is an array of data values/objects returned by the ajax calls. 
     console.log("Handling data ..."); 
     ... 
    }; 

    // The 'run' method 
    var run = function() { 
     getScenario() 
     .then(checkData) 
     .then(waitForTimeout) 
     .then(callApi) 
     .then(handleResults) 
     .then(null, function(reason) { 
      console.error(reason); 
     }) 
     .then(run); 
    }; 

    return { 
     run : run 
    } 
})(jQuery); 

App.run(); 

這將停止錯誤,但可以很容易地適應不斷。

+0

感謝所有的上下文,爲什麼我目前的迭代沒有工作。我有這個在這裏:http://jsfiddle.net/858m3mL5/1/ ...我修正了一些語法錯誤,但目前在這裏看到一些奇怪的行爲。基本上,在i .map()終結點之後...我無法將正確的對象屬性應用於AJAX請求。不知道爲什麼會這樣,任何見解都會有幫助。 – richie 2015-03-03 22:47:54

+0

啊,意識到這只是AJAX請求的URL屬性中的一個錯字。你可以看到它在這裏完全工作:http://jsfiddle.net/858m3mL5/1/ ...再次感謝解決方案。使用jQuery(不需要額外的依賴)並且貼近我原來的結構。 – richie 2015-03-03 23:00:43

+0

啊對,錯字。良好的錯誤報告應該有助於解決這類錯誤和其他問題。 – 2015-03-04 16:29:05

1

我會嘗試用KrisKowal的q來回答你的問題,因爲我對jQuery產生的承諾不甚熟悉。

首先,我不知道你是否想解決的承諾的陣列中串聯或並聯,所提出的解決方案,我解決了所有這些並行:),解決他們在系列中,我會使用Q的reduce

function getScenario() { ... } 

function ajaxRequest(instruction) { ... } 

function createPromisifiedInstruction(instruction) { 
    // delay with frequency, not sure why you want to do this :(
    return Q.delay(instruction.frequency) 
    .then(function() { 
     return this.ajaxRequest(instruction); 
    }); 
} 

function run() { 
    getScenario() 
    .then(function (data) { 
     var promises = []; 
     var instruction; 
     var i; 
     for (i = 0; i < data.endpoints.length; i += 1) { 
     instruction = { 
      method: data.endpoints[i].method, 
      type: data.endpoints[i].type, 
      endpoint: data.endpoints[i].endPoint, 
      frequency: data.base.frequency 
     }; 
     promises.push(createPromisifiedInstruction(instruction)); 
     } 
     // alternative Q.allSettled if all the promises don't need to 
     // be fulfilled (some of them might be rejected) 
     return Q.all(promises); 
    }) 
    .then(function (instructionsResults) { 
     // instructions results is an array with the result of each 
     // promisified instruction 
    }) 
    .then(run) 
    .done(); 
} 

run(); 

好吧,讓我來解釋一下上面的解決方案:

  1. 首先是假設getScenario讓你開始與最初的JSON(實際上返回一個與JSON解析承諾)
  2. 創建的每個指令
  3. promisify每條指令的結構,讓每一個實際上是一個承諾,其 分辨率值將承諾通過ajaxRequest
  4. ajaxRequest回到返回一個承諾,其分辨率值的結果請求,這也就意味着createPromisifiedInstruction的分辨率值將會是分辨率的值ajaxRequest
  5. 返回一個單一的承諾與Q.all,它實際上做的是履行自己時,它所建立的所有承諾解決:),如果其中之一他們失敗了,你真的需要解決的承諾,反正使用Q.allSettled
  6. 做任何你想要與所有的先前承諾的分辨率值,注意instructionResults是一個數組控股各承諾的分辨率值在他們宣佈

參考順序:KrisKowal's Q

1

您在waitForTimeout函數的循環中有一個return語句。這意味着該函數將在循環的第一次迭代之後返回,這就是您出錯的地方。

您還使用遞延反模式和使用承諾在地方,你不需要他們。你並不需要從then處理程序返回一個承諾,除非有什麼東西等待。

關鍵是你需要將映射到一個承諾。 Array#map是完美的。並請使用正確的承諾庫,not jQuery promises編輯但如果你絕對必須使用jQuery的承諾...):

var App = (function ($) { 
    // Gets the scenario from the API 
    // NOTE: this returns a promise 
    var getScenario = function() { 
     console.log('Getting scenario ...'); 
     return $.get('http://demo3858327.mockable.io/scenario'); 
    }; 

    // mapToInstructions is basically unnecessary. each instruction does 
    // not need its own timeout if they're all the same value, and you're not 
    // reshaping the original values in any significant way 

    // This wraps the setTimeout into a promise, again 
    // so we can chain it 
    var waitForTimeout = function(data) { 
     var d = $.Deferred(); 
     setTimeout(function() { 
      d.resolve(data.endpoints); 
     }, data.base.frequency); 
     return d.promise(); 
    }; 

    var callApi = function(instruction) { 
     return $.ajax({ 
      type: instruction.method, 
      dataType: instruction.type, 
      url: instruction.endPoint 
     }); 
    }; 

    // Final step: call the API from the 
    // provided instructions 
    var callApis = function(instructions) { 
     console.log(instructions); 
     console.log('Calling API with given instructions ...'); 
     return $.when.apply($, instructions.map(callApi)); 
    }; 

    var handleResults = function() { 
     var data = Array.prototype.slice(arguments); 
     console.log("Handling data ..."); 
    }; 

    // The 'run' method 
    var run = function() { 
     getScenario() 
     .then(waitForTimeout) 
     .then(callApis) 
     .then(handleResults) 
     .then(run); 
    }; 

    return { 
     run : run 
    } 
})($); 

App.run(); 
+0

jQuery的承諾有他們的問題,但這個工作100%OK。 – 2015-03-03 09:46:29

+0

@ Roamer-1888就我而言,他們對於任何涉及多項單一承諾的工作都不是百分百的。缺少一個'Promise.all()'方法本身使其成爲這項任務的理想工具,撇開所有其他的巨大問題。 – JLRishe 2015-03-03 09:52:51

+0

並非如此。在jQuery中它是'$ .when()'。 – 2015-03-03 10:01:38

1

利用內setTimeoutdeferred.notifyNumber(settings.frequency) * (1 + key)setTimeout持續時間嘗試; msgdeferred.notify記錄到consoledeferred.progress回調,第三函數參數內.then以下超時

var App = (function ($) { 
 
    
 
     var getScenario = function() { 
 
      console.log("Getting scenario ..."); 
 
      return $.get("http://demo3858327.mockable.io/scenario2"); 
 
     }; 
 
     
 
     var mapToInstruction = function (data) { 
 
      var res = $.map(data.endpoints, function(settings, key) { 
 
       return { 
 
        method:settings.method, 
 
        type:settings.type, 
 
        endpoint:settings.endPoint, 
 
        frequency:data.base.frequency 
 
       } 
 
      }); 
 
      
 
      console.log("Instructions recieved:", res); 
 
    
 
      return res 
 
     }; 
 
     
 
     var waitForTimeout = function(instruction) {   
 
       var res = $.when.apply(instruction, 
 
        $.map(instruction, function(settings, key) { 
 
        return new $.Deferred(function(dfd) {      
 
         setTimeout(function() { 
 
          dfd.notify("Waiting for " 
 
            + settings.frequency 
 
            + " ms") 
 
         .resolve(settings);     
 
         }, Number(settings.frequency) * (1 + key)); 
 
        }).promise() 
 
       }) 
 
       ) 
 
       .then(function() { 
 
       return this 
 
       }, function(err) { 
 
       console.log("error", err) 
 
       } 
 
       , function(msg) { 
 
       console.log("\r\n" + msg + "\r\nat " + $.now() + "\r\n") 
 
       }); 
 
       return res 
 
     }; 
 
    
 
     var callApi = function(instruction) { 
 
      console.log("Calling API with given instructions ..." 
 
         , instruction); 
 
      var res = $.when.apply(instruction, 
 
       $.map(instruction, function(request, key) { 
 
       return request.then(function(settings) { 
 
        return $.ajax({ 
 
        type: settings.method, 
 
        dataType: settings.type, 
 
        url: settings.endpoint 
 
        }); 
 
       })              
 
       }) 
 
      ) 
 
      .then(function(data) { 
 
       return $.map(arguments, function(response, key) {    
 
        return response[0] 
 
       }) 
 
      }) 
 
      return res 
 
     }; 
 
     
 
     var handleResults = function(data) { 
 
      console.log("Handling data ..." 
 
         , JSON.stringify(data, null, 4)); 
 
      return data 
 
     }; 
 
     
 
     var run = function() { 
 
      getScenario() 
 
      .then(mapToInstruction) 
 
      .then(waitForTimeout) 
 
      .then(callApi) 
 
      .then(handleResults) 
 
      .then(run); 
 
     }; 
 
     
 
     return { 
 
      // This will expose only the run method 
 
      // but will keep all other functions private 
 
      run : run 
 
     } 
 
    })($); 
 
    
 
    // ... And start the app 
 
    App.run();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"> 
 
</script>

的jsfiddle http://jsfiddle.net/3Lddzp9j/13/