2016-09-12 45 views
2

我在這裏有兩個文件。 query.php是我寫的處理這個特定網站的所有MySQL查詢的腳本。 query_test.js是一系列AJAX調用來測試query.php。正如你將在下面看到的,使用Promises將一個接一個的測試序列化,因爲我已經使測試依賴於另一個測試。通過寫入控制檯,AJAX呼叫出現始終按照正確的順序完成。問題在於輸出不一致,單個測試的輸出結果並不總是反映出他們面前的測試結果。由於單個測試似乎工作得很好,我堅信某些事情是排序問題。我無法弄清楚爲什麼會發生這種情況。由JS Promises排序的AJAX調用似乎以正確的順序運行,但數據庫的輸出表明不然

爲了清楚起見,測試如下:

  • 測試1:檢索整個表格,顯示
  • 測試2:添加新行,然後檢索和顯示該表再次
  • 測試3:選擇剛剛添加的行,顯示結果
  • 測試4:更新剛剛添加的行,檢索並顯示錶
  • 測試5:刪除剛剛添加的行,retr ieve和顯示錶

在下面的示例輸出,注意試驗2後檢索的表不反映加入一排與試驗3的查詢返回什麼。直到測試4,以前添加的行似乎顯示出來。每次執行產生不同的輸出;有時甚至是完全正常的,而其他時間的確像上面那樣錯誤。控制檯輸出總是顯示每個呼叫按其應該的順序排列。這讓我覺得後端的東西出了問題,但我對這是什麼感到無能爲力。

query.php

<?php 
/** 
The purpose of this script is to query the database in a number of ways. 
This script should be called via AJAX. See below for supported actions and 
required parameters for each. 

-------- ACTIONS ($_POST["action"])----------- 

1. GET_TABLE: retrieve an entire table 
    - Parameters: 
     "table_name": [String] name of the table 
    - Returns: [JSON] the entire table 

2. UPDATE_TABLE: update a row in a table 
    - Parameters: 
     "table_name": [String] name of the table 
     "queries": [array] a list of queries, like so: <column>[<relational_operator]<value> to find rows to update 
     "values": [array] key=>value pairs for each column to be updated. 

3. SELECT_TABLE: select specified columns from specified rows 
    - Parameters: 
     "table_name": [String] name of the table 
     "queries": [array] a list of queries, like so: <column>[<relational_operator]<value> 
     "columns": (optional) [array] a list of column names to be returned. default value is '*', or all columns 
    - Returns: [JSON] the rows returned from the query 

4. ADD_ROW: add a row to a table 
    - Parameters: 
     "table_name": [String] name of the table 
     "values": [array] key=>value pairs of columns names and corresponding values for the new row 

5. REMOVE_ROW: remove a row or rows from a table 
    - Parameters: 
     "table_name": [String] name of the table 
     "queries": [array] a list of queries, like so: <column>[<relational_operator]<value> 

**/ 

// Constants 
// Actions 
define("GET_TABLE", 100); 
define("UPDATE_TABLE", 101); 
define("SELECT_TABLE", 102); 
define("ADD_ROW", 103); 
define("REMOVE_ROW", 104); 

$server = "localhost"; 
$username = "root"; 
$password = "password"; 
$db_name = "test"; 

$conn = new mysqli($server, $username, $password, $db_name); 
if ($conn->connect_error) die("Connection failed: " . $conn->connect_error); 

// Action must be set 
checkPOST("action") or die("Error: POST variable 'action' must be setand not empty."); 
$action = $_POST["action"]; 

// Table name must be given for all actions 
checkPOST("table_name") or die("Error: POST variable 'table_name' must be set and not empty."); 
$table = $_POST["table_name"]; 

// Generic error message 
$param_err = "Error: ensure all required params are set and not empty."; 

// See which action needs to be done 
switch ($action) { 
    case GET_TABLE: 
     // Simply run the query 
     $q = "SELECT * FROM $table"; 
     $result = $conn->query($q); 
     $result or die("Query '" . $q . "' failed: " . $conn->error); 
     if ($result->num_rows > 0) { 
      $output = array(); 
      while ($row = $result->fetch_assoc()) { 
       $output[] = $row; 
      } 
      echo json_encode($output); 
     } 
     break; 
    case UPDATE_TABLE: 
     // Check for additional required params 
     (checkPOST("values") && checkPOST("queries")) 
      or die($param_err); 
     $values = $_POST["values"]; 
     $queries = $_POST["queries"]; 

     $q = "UPDATE $table"; 

     // Add the values to be set to the query 
     $q .= " SET "; 
     addQuotesToStrings($values); 
     addItemsToQuery($q, $values, true); 

     // Add the WHERE clause at the end of the query 
     $q .= " WHERE "; 
     addItemsToQuery($q, $queries, false); 

     // Now ready to send off the query to the db and report success or failure 
     $conn->query($q) or die("Query '" . $q . "' failed: " . $conn->error); 
     echo "Successfully updated " . $conn->affected_rows . " rows."; 

     break; 
    case SELECT_TABLE: 
     // Check for additional required params 
     checkPOST("queries") or die($param_err); 
     $queries = $_POST["queries"]; 

     $q = "SELECT "; 

     // Add columns if specified 
     if (checkPOST("columns")) { 
      $columns = $_POST["columns"]; 
      addItemsToQuery($q, $columns, false); 
     } 
     else $q .= "* "; // No columns specified. Select all 

     // Add table name 
     $q .= "FROM $table "; 

     // Add queries 
     $q .= "WHERE "; 
     addItemsToQuery($q, $queries, false); 

     // Now, send off query 
     $result = $conn->query($q); 
     $result or die("Query '" . $q . "' failed: " . $conn->error); 
     if ($result->num_rows > 0) { 
      $output = array(); 
      while ($row = $result->fetch_assoc()) { 
       $output[] = $row; 
      } 
      echo json_encode($output); 
     } 

     break; 
    case ADD_ROW: 
     // Check for POST var "values" 
     checkPOST("values") or die($param_err); 
     $values = $_POST["values"]; 

     $q = "INSERT INTO $table"; 

     // First, add column names 
     $q .= " ("; 
     addItemsToQuery($q, array_keys($values), false); 
     $q .= ") "; 

     // Add the values 
     $q .= "VALUES ("; 
     addQuotesToStrings($values); 
     addItemsToQuery($q, $values, false); 
     $q .= ")"; 

     // Run the query 
     $conn->query($q) or die("Query '" . $q . "' failed: " . $conn->error); 
     echo "Query was successful."; 

     break; 
    case REMOVE_ROW: 
     // Check for queries 
     checkPOST("queries") or die($param_err); 
     $queries = $_POST["queries"]; 

     $q = "DELETE FROM $table"; 

     // Add queries 
     $q .= " WHERE "; 
     addItemsToQuery($q, $queries, false); 

     // Run query 
     $conn->query($q) or die("Query '" . $q . "' failed: " . $conn->error); 
     echo "Query affected " . $conn->affected_rows . " rows."; 

     break; 
    default: 
     die("Error: POST variable 'action' has an unknown value."); 
} 

/** 
    Adds items from an array to an SQL query string 
    Assumes a space is present before the last keyword of the existing query. 

    @param string &$q  A reference to an SQL query string 
    @param array $items An array containing strings that need to be added to a query in a list format (e.g. item1,item2,item3) 
    $param boolean $pairs A boolean that indicated whether the items are key=>value pairs or not 
**/ 
function addItemsToQuery(&$q, $items, $pairs) { 
    $first = true; 
    foreach ($items as $name => $item) { 
     if (!$first) $q .= ", "; 
     else $first = false; 
     $q .= $pairs ? $name . "=" . $item : $item; 
    } 
} 

/** 
    Adds single quotes to each string in a array of items for the purpose of being added to a MySQL query 

    @param array $values A reference to an array of items 
**/ 
function addQuotesToStrings(&$values) { 
    foreach ($values as &$value) { 
     if (strcmp(gettype($value), "string") == 0) $value = "'" . $value . "'"; 
    } 
    unset($value); 
} 

/** 
    Simple helper function to check if a POST var is set and not empty 

    @param string $name The name of the POST variable 
**/ 
function checkPOST($name) { 
    return isset($_POST[$name]) && !empty($_POST[$name]); 
} 
?> 


query_test.js

// This script is a series of AJAX calls to test query.php 

const GET_TABLE = 100; 
const UPDATE_TABLE = 101; 
const SELECT_TABLE = 102; 
const ADD_ROW = 103; 
const REMOVE_ROW = 104; 

/** 
    Runs an AJAX request to query.php and displays the result 

    @param {jQuery} div    A jQuery object div to place results 
    @param {array}  params   An assoc. array of POST variables for the AJAX call 
    @param {boolean} displayAsTable Determines where the result will be displayed as a table or as it is 

    @return {Promise} A Promise object 
**/ 
function runTest(div, params, displayAsTable) { 
    return Promise.resolve($.post("../php/query.php", params, function(data) { 
     // Display result 
     if (displayAsTable) { 
      let tableData = JSON.parse(data); 
      let table = $('<table></table>'); 
      for (let rowKey in tableData) { 
       let row = $('<tr></tr>'); 
       for (let colKey in tableData[rowKey]) { 
        let col = $('<td></td>'); 
        col.html(tableData[rowKey][colKey]); 
        row.append(col); 
       } 
       table.append(row); 
      } 
      div.append(table); 
     } 
     else div.append($('<p>' + data + '</p>')); 
    })); 
} 

var num = 0; 
// Temporary function to test if Promise order holds up 
function logOrder(test) { 
    num++; 
    console.log(test + " finished: " + num); 
} 

// ----------- Test 1: Retrieve table ---------------- 
let div = $('<div></div><br>'); 
let header = $('<h2>Test 1: Retrieve table</h2>'); 
div.append(header); 
$('body').append(div); 
let params = {action: GET_TABLE, table_name: "projects"}; 
var promise = runTest(div, params, true); 

// ------------ Test 2: Add Row ---------------------- 
promise = promise.then(function(value) { 
    logOrder("Test 1"); 

    let div = $('<div></div><br>'); 
    let header = $('<h2>Test 2: Add Row</h2>'); 
    div.append(header); 
    $('body').append(div); 
    let values = {title: "test_proj", pic: "none.jpg", brief: "testing", description: "this is a test"}; 
    let params = {action: ADD_ROW, values: values, table_name: "projects"}; 
    runTest(div, params, false); 
}, function(error) { 
    alert(error); 
}); 

// Get table to ensure row has been added 
promise = promise.then(function(value) { 
    logOrder("Test 2"); 

    let div = $('<div></div><br>'); 
    $('body').append(div); 
    let params = {action: GET_TABLE, table_name: "projects"}; 
    runTest(div, params, true); 
}, function(error) { 
    alert(error); 
}); 

// -------------- Test 3: Select table ------------------- 
promise = promise.then(function(value) { 
    logOrder("Test 2 Check"); 

    let div = $('<div></div><br>'); 
    let header = $('<h2>Test 3: Select Table</h2>'); 
    div.append(header); 
    $('body').append(div); 
    let queries = ["title='test_proj'"]; 
    let params = {action: SELECT_TABLE, table_name: "projects", queries: queries}; 
    runTest(div, params, true); 
}, function(error) { 
    alert(error); 
}); 

// -------------- Test 4: Update table ------------------- 
promise = promise.then(function(value) { 
    logOrder("Test 3"); 

    let div = $('<div></div><br>'); 
    let header = $('<h2>Test 4: Update Table</h2>'); 
    div.append(header); 
    $('body').append(div); 
    let queries = ["title='test_proj'"]; 
    let values = {brief: "This was updated", description: "This was also updated"}; 
    let params = {action: UPDATE_TABLE, table_name: "projects", queries: queries, values: values}; 
    runTest(div, params, false); 
}, function(error) { 
    alert(error); 
}); 

// Get table to ensure table has been updated 
promise = promise.then(function(value) { 
    logOrder("Test 4"); 

    let div = $('<div></div><br>'); 
    $('body').append(div); 
    let params = {action: GET_TABLE, table_name: "projects"}; 
    runTest(div, params, true); 
}, function(error) { 
    alert(error); 
}); 

// -------------- Test 5: Remove Row(s) ------------------ 
promise = promise.then(function(value) { 
    logOrder("Test 4 Check"); 

    let div = $('<div></div><br>'); 
    let header = $('<h2>Test 5: Remove Row(s)</h2>'); 
    div.append(header); 
    $('body').append(div); 
    let queries = ["title='test_proj'"]; 
    let params = {action: REMOVE_ROW, table_name: "projects", queries: queries}; 
    runTest(div, params, false); 
}, function(error) { 
    alert(error); 
}); 

// Get table to ensure row has been removed 
promise = promise.then(function(value) { 
    logOrder("Test 5"); 

    let div = $('<div></div>'); 
    $('body').append(div); 
    let params = {action: GET_TABLE, table_name: "projects"}; 
    runTest(div, params, true); 
}, function(error) { 
    alert(error); 
}).then(function(value) { 
    logOrder("Test 5 Check"); 
}, function(error) { 
    alert(error); 
}); 


輸出示例:

Test 1: Retrieve table 

11 placeholder none.jpg placeholder placeholder description 

Test 2: Add Row 

Query was successful. 

11 placeholder none.jpg placeholder placeholder description 

Test 3: Select Table 


Test 4: Update Table 

Successfully updated 1 rows. 

11 placeholder none.jpg placeholder placeholder description 
17 test_proj none.jpg This was updated This was also updated 

Test 5: Remove Row(s) 

Query affected 1 rows. 

11 placeholder none.jpg placeholder placeholder description 

回答

1

我在看着this後發現自己做錯了。

我做了@DarkKnight建議的更改,但這不是問題,或者至少它不是唯一的問題。

原來我只需要在每個promise.then()成功函數中返回一個承諾。本來,我沒有這樣做。因此,在第一次測試之後,每個後續測試都開始查詢,但隨後調用下一個then()而不是等待結果,因爲新的承諾沒有做任何事情。

這是新query_test.js

// This script is a series of AJAX calls to test query.php 

const GET_TABLE = 100; 
const UPDATE_TABLE = 101; 
const SELECT_TABLE = 102; 
const ADD_ROW = 103; 
const REMOVE_ROW = 104; 

/** 
    Runs an AJAX request to query.php and displays the result 

    @param {jQuery} div    A jQuery object div to place results 
    @param {array}  params   An assoc. array of POST variables for the AJAX call 
    @param {boolean} displayAsTable Determines where the result will be displayed as a table or as it is 

    @return {Promise} A Promise object 
**/ 
function runTest(div, params, displayAsTable) { 
    return new Promise(function(resolve, reject) { 
     $.post("../php/query.php", params, function(data) { 
      // Display result 
      if (displayAsTable) { 
       let tableData = JSON.parse(data); 
       let table = $('<table></table>'); 
       for (let rowKey in tableData) { 
        let row = $('<tr></tr>'); 
        for (let colKey in tableData[rowKey]) { 
         let col = $('<td></td>'); 
         col.html(tableData[rowKey][colKey]); 
         row.append(col); 
        } 
        table.append(row); 
       } 
       div.append(table); 
      } 
      else div.append($('<p>' + data + '</p>')); 
     }).then(resolve, reject) 
    }); 
} 

var num = 0; 
// Temporary function to test if Promise order holds up 
function logOrder(test) { 
    num++; 
    console.log(test + " finished: " + num); 
} 

var promise = Promise.resolve(); 

// ----------- Test 1: Retrieve table ---------------- 
promise = promise.then(function(value) { 
    let div = $('<div></div><br>'); 
    let header = $('<h2>Test 1: Retrieve table</h2>'); 
    div.append(header); 
    $('body').append(div); 
    let params = {action: GET_TABLE, table_name: "projects"}; 
    return runTest(div, params, true); 
}, function(error) { 
    alert(error); 
}); 

// ------------ Test 2: Add Row ---------------------- 
promise = promise.then(function(value) { 
    logOrder("Test 1"); 

    let div = $('<div></div><br>'); 
    let header = $('<h2>Test 2: Add Row</h2>'); 
    div.append(header); 
    $('body').append(div); 
    let values = {title: "test_proj", pic: "none.jpg", brief: "testing", description: "this is a test"}; 
    let params = {action: ADD_ROW, values: values, table_name: "projects"}; 
    return runTest(div, params, false); 
}, function(error) { 
    alert(error); 
}); 

// Get table to ensure row has been added 
promise = promise.then(function(value) { 
    logOrder("Test 2"); 

    let div = $('<div></div><br>'); 
    $('body').append(div); 
    let params = {action: GET_TABLE, table_name: "projects"}; 
    return runTest(div, params, true); 
}, function(error) { 
    alert(error); 
}); 

// -------------- Test 3: Select table ------------------- 
promise = promise.then(function(value) { 
    logOrder("Test 2 Check"); 

    let div = $('<div></div><br>'); 
    let header = $('<h2>Test 3: Select Table</h2>'); 
    div.append(header); 
    $('body').append(div); 
    let queries = ["title='test_proj'"]; 
    let params = {action: SELECT_TABLE, table_name: "projects", queries: queries}; 
    return runTest(div, params, true); 
}, function(error) { 
    alert(error); 
}); 

// -------------- Test 4: Update table ------------------- 
promise = promise.then(function(value) { 
    logOrder("Test 3"); 

    let div = $('<div></div><br>'); 
    let header = $('<h2>Test 4: Update Table</h2>'); 
    div.append(header); 
    $('body').append(div); 
    let queries = ["title='test_proj'"]; 
    let values = {brief: "This was updated", description: "This was also updated"}; 
    let params = {action: UPDATE_TABLE, table_name: "projects", queries: queries, values: values}; 
    return runTest(div, params, false); 
}, function(error) { 
    alert(error); 
}); 

// Get table to ensure table has been updated 
promise = promise.then(function(value) { 
    logOrder("Test 4"); 

    let div = $('<div></div><br>'); 
    $('body').append(div); 
    let params = {action: GET_TABLE, table_name: "projects"}; 
    return runTest(div, params, true); 
}, function(error) { 
    alert(error); 
}); 

// -------------- Test 5: Remove Row(s) ------------------ 
promise = promise.then(function(value) { 
    logOrder("Test 4 Check"); 

    let div = $('<div></div><br>'); 
    let header = $('<h2>Test 5: Remove Row(s)</h2>'); 
    div.append(header); 
    $('body').append(div); 
    let queries = ["title='test_proj'"]; 
    let params = {action: REMOVE_ROW, table_name: "projects", queries: queries}; 
    return runTest(div, params, false); 
}, function(error) { 
    alert(error); 
}); 

// Get table to ensure row has been removed 
promise = promise.then(function(value) { 
    logOrder("Test 5"); 

    let div = $('<div></div>'); 
    $('body').append(div); 
    let params = {action: GET_TABLE, table_name: "projects"}; 
    return runTest(div, params, true); 
}, function(error) { 
    alert(error); 
}); 

promise.then(function(value) { 
    logOrder("Test 5 Check"); 
}, function(error) { 
    alert(error); 
}); 
+0

您所看到的差異完全是由於添加這些'返回的runTest(...);'語句。 'runTest()'不需要改變 - 它在mod之前和之後返回一個promise。更好的辦法是簡單地編寫'return $ .post(...);'。外面的'新承諾(...)'是完全沒有必要的。 –

+1

謝謝澄清。通過將'runTest()'改回原來的形式,我可以驗證你是正確的。我確定返回'$ .post()'的結果也可以,但我的意圖是使用本地Javascript Promise對象,而不是jQuery延遲對象。 – printlnlevi

0

runTest()Promise.resolve創建一個Promise解析爲一個jQuery Deferred對象立即,所以這些AJAX調用實際上並行啓動。

爲了讓AJAX調用在系列開始,runTest()應該返回一個Promise解析的結果,而不是由converting jQuery的遞延對象承諾:

function runTest(div, params, displayAsTable) { 
    return new Promise(function (resolve, reject) { 
     $.post("../php/query.php", params, function(data) { 
      // Display result 
     }).then(resolve, reject) 
    }) 
} 

,或者你可以嘗試jQuery的3.0Deferred應與Promise工作無需轉換。