2016-09-12 42 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工作无需转换。