Ваша query
-функция является асинхронной функцией, которая возвращает результат посредством обратного вызова, который может быть вызван когда-нибудь в будущем. Вы должны подождать, пока не будет вызван этот обратный вызов, прежде чем продолжить. Один из способов решить эту проблему - заставить собственную функцию вызвать обратный вызов с ошибкой и данными:
const myFakedata = {
1: { ref_byid: null },
2: { ref_byid: null },
3: { ref_byid: 1 },
4: { ref_byid: 3 },
5: { ref_byid: 6 },
6: { ref_byid: 4 }
};
// Fake query for test.
function query(sql, parameters, callback) {
setTimeout(() => callback(null, [myFakedata[parameters[0]]]), 50);
}
function rersolveHierarchy(callback) {
try {
var path = [];
var sql = "select ref_by_id from users where id=$1";
function loop(currentId) {
try {
if (currentId === null) {
//Loop finished
return callback(null, path); // breaks the loop
}
currentId = +currentId;
if (isNaN(currentId)) return callback(Error("currentId is not a number"));
path.push(currentId);
console.log("Search for", currentId);
query(sql, [currentId], function(error, rows) {
if (error) return callback(error);
if (!rows || rows.length === 0) return callback(Error(`No rows for id ${currentId}`));
if (rows.length > 1) return callback(Error(`Multiple rows for id ${currentId}`));
loop(rows[0].ref_byid); // extract id for next loot iteration
});
} catch (error) {
return callback(error);
}
}
var hey = { ref_by_id: 5 }; // Test data
loop(hey.ref_by_id); // Start loop
} catch (error) {
return callback(error);
}
}
rersolveHierarchy(function(error, data) {
if (error) return console.error(error);
console.log(data.join(", "));
});
Это работает, но вы заметите, что вы не можете получить информацию из функции без обратного вызова. А как насчет ошибок? Если вы хотите, чтобы о них сообщали из функции, вам также нужно добавить обратный вызов для этого. И вам нужно объявить try / catch на ошибки перехвата, которые могут произойти, когда вы запускаете свой код, или ваш вызывающий никогда не будет уведомлен. Это быстро становится грязным.
Еще один способ решить эту проблему - Promise и asyn c с await . Обещание - это стандартизированный способ обработки асинхронных функций, позволяющий легко получить результат или ошибку позже в коде. Оператор await
(добавлен в ES2017) ожидает разрешения для разрешения или выдает ошибку, если она была отклонена. Это облегчает использование асинхронных функций.
Вот пример:
const myFakedata = {
1: { ref_byid: null },
2: { ref_byid: null },
3: { ref_byid: 1 },
4: { ref_byid: 3 },
5: { ref_byid: 6 },
6: { ref_byid: 4 }
};
// Fake query for test.
function query(sql, parameters, callback) {
setTimeout(() => callback(null, [myFakedata[parameters[0]]]), 50);
}
// Promise-wrapper for your query function.
// If there is an error the promise will be rejected
function pquery(sql, parameters) {
return new Promise((resolve, reject) =>
// calling your original query
query(
sql,
parameters,
// Callback function. if there is an error, reject the promise, otherwise resolve it
(error, result) => (error ? reject(error) : resolve(result))
)
);
}
async function rersolveHierarchy(id) {
var path, rows, currentId;
const sql = "select ref_by_id from users where id=$1";
path = []; // Start with empty array
currentId = id;
while (currentId !== null) {
// make sure currentId is a number by using the unary plus operator
currentId = +currentId;
if (isNaN(currentId)) throw Error("currentId is not a number");
// Check for infinity loop
if (path.includes(currentId)) throw Error(`Loop detected: ${currentId}: ${path.join(",")}`);
// What order do you want the items to be?
// use "push" to add the latest to the end,
// or "unshift" to add it at the begining
path.push(currentId); //or path.unshift(currentId);
console.log("Search for", currentId);
rows = await pquery(sql, [currentId]); // Get next item
// Check that only one row is returned.
if (!rows || rows.length === 0) throw Error(`No rows for id ${currentId}`);
if (rows.length > 1) throw Error(`Multiple rows for id ${currentId}`);
currentId = rows[0].ref_byid; // extract id for next loot iteration
}
return path;
}
// Your function where you call resolveUserPath
async function myFunction() {
const hey = { ref_by_id: 5 }; // Test data
const path = await rersolveHierarchy(hey.ref_by_id);
console.log("Path:", path.join(", "));
return path;
}
// Always check for errors
myFunction().catch(error => console.error(error));