Вход

Просмотр полной версии : Не могу разобраться с промисами


asdffak
10.02.2021, 22:42
Только недавно сел разбираться с промисами, выглядит все довольно сложно и непонятно. Казалось бы только уловил суть, написал код в соответсвие со своим понимание, но не работает.
Точнее работает, но нет асинхронности.

function check(input) {
let err = true;
let email_promise = new Promise(function(resolve, reject) {
let xml = new XMLHttpRequest();
xml.open("POST", "script/LoginCheck.php", true);
xml.send();
xml.onreadystatechange = function() {
if (xml.readyState == 4 && xml.status == 200) {
let rm = /^[\w\.\d-_]+@[\w\.\d]+\.[\w\.\d]+$/i;
if (xml.responseText == true && rm.test(input.value)) resolve();
else reject();
}
}
});
email_promise.then(function() {
err = true;
},
function() {
err = false;
alert("Такой адрес электронной почты уже зарегистрирован");
});
return err;
}

По задумке этот кусок кода проверяет 1) Действительно ли значение в input является почтой, 2) Что такой адрес еще не зарегистрирован (проверка через запрос на LoginCheck.php).
И впринципе при однократном вызове (при заполнении соответствующего поля) все отрабатывает правильно. Однако дальше, при завершении регистрации, возникает проблема:

var registrationDATA = new FormData();
function Registration() {
let inputs = document.querySelectorAll("input");
let err = true;
for (let input of inputs) {
if (!check(input)) err = false;
else registrationDATA.set(input.name, input.value);
}
if (err) alert("Не все поля заполнены корректно");
}

Почему-то в последнем условии выполняется именно вариант else при чем промис отрабатывает так, словно работает синхронно (т.е. сначала завершается выполнение функции Registration, а затем появляется сообщение "Такой адрес уже зарегистрирован")

P.S. Аналогично пробовал реализовать вызов функции check через промис в последнем условии, но с тем же результатом.

var registrationDATA = new FormData();
function Registration() {
let inputs = document.querySelectorAll("input");
let err = true;
for (let input of inputs) {
let check_promise = new Promise(function(resolve, reject) {
if (!check(input)) resolve(true);
else registrationDATA.set(input.name, input.value);
});
check_promise.then(function(result) {err = result;});
}
if (err) alert("Не все поля заполнены корректно");
}


P.P.S. Да, там ошибка в логике "Такой адрес зарегистрирован" будет появляться даже в случае, когда строка просто не является адресом электронной почты. Но это не влияет на описанную проблему.

voraa
10.02.2021, 23:04
Строка 22 return err;
будет выполняться синхронно и всегда возвращать true
Асинхронно будут выполняться только функции в стр. 15-17 в случае успеха и 18-21 в случае ошибки.

В асинхронном программировании приходится делать асинхронным все, что зависит от первой асинхронной операции
Поэтому функция chek должна возвращать не конкретное значение, а свой промис


function check(input) {
return new Promise(function(resolve, reject) {
let xml = new XMLHttpRequest();
xml.open("POST", "script/LoginCheck.php", true);
xml.send();
xml.onreadystatechange = function() {
if (xml.readyState == 4 && xml.status == 200) {
let rm = /^[\w\.\d-_]+@[\w\.\d]+\.[\w\.\d]+$/i;
if (xml.responseText == true && rm.test(input.value)) resolve();
else reject();
}
}
})
.then(
function() {
return true;
},
function() {
alert("Такой адрес электронной почты уже зарегистрирован");
return false
}
);
}



и вызывать ее
check(input).then (function (result) {/*result будет true или false */})

Или в асинхронной функции
let result = await check(input)
/*result будет true или false тут его обрабатываем*/

asdffak
11.02.2021, 00:26
Блин, как сложна та!
В любом случае спасибо за помощь.

Интересно, а есть вариант решить ту же задачу без промисов? Или это самый не костыльный вариант?
Потому что на мой взгляд код выглядит как-то перегруженно и трудно читаем.
Кроме того задача оказывается нескольо сложнее, поскольку я привел лишь часть кода и рально функция check проверяет вообще все input'ы согласно их имени, но запрос на сервер предусмотрен только в одном случае:

function check(input) {
let err = true;
switch(input.name) {
case "email":
let email_promise = new Promise(function(resolve, reject) {
let xml = new XMLHttpRequest();
xml.open("POST", "script/LoginCheck.php", true);
xml.send();
xml.onreadystatechange = function() {
if (xml.readyState == 4 && xml.status == 200) {
let rm = /^[\w\.\d-_]+@[\w\.\d]+\.[\w\.\d]+$/i;
if (xml.responseText == true && rm.test(input.value)) resolve();
else reject();
}
}
});
email_promise.then(function() {
err = true;
},
function() {
err = false;
alert("Такой адрес электронной почты уже зарегистрирован");
});
break;

case "..."
if(...) ...;
else err = false;
break

case "..."
break;
......

return err;
}

Т.е. в каждом кейсе выполняется проверка и, если поле его не проходит, то err = false.
Это что, получается мне нужно будет все кейсы оборачивать во внутренний промис?

voraa
11.02.2021, 08:01
Это что, получается мне нужно будет все кейсы оборачивать во внутренний промис?
Как бы да.
Но для этого существуют асинхронные функции (async/await)
Функция объявленная async всегда возвращает промис. Если ей в return указать промис, она его и вернет. А если какое то другое значение, то она обернет его в Promise.resolve(value)

await получает промис, ждет его разрешение и вытаскивает значение, которое было передано через resolve. Неудобно только, что значение переданное через reject так не получить. поэтому их приходится ловить с помощью try{} catch. Но через reject лучше передавать настоящие ошибки.
Ваш код можно переписать так

async function check(input) {
let err = true;
switch(input.name) {
case "email":
return new Promise(function(resolve, reject) {
let xml = new XMLHttpRequest();
xml.open("POST", "script/LoginCheck.php", true);
xml.send();
xml.onreadystatechange = function() {
if (xml.readyState == 4 && xml.status == 200) {
let rm = /^[\w\.\d-_]+@[\w\.\d]+\.[\w\.\d]+$/i;
if (xml.responseText == true && rm.test(input.value)) resolve(true);
else resolve (false);
}
}
xml.onerror = function() { reject () }

});
.then(function(err) {
if (err) alert("Такой адрес электронной почты уже зарегистрирован");
return err
}
);
break;

case "..."
if(...) ...;
else err = false;
break

case "..."
break;
......

return err;
}

var registrationDATA = new FormData();

async function Registration() {
let inputs = document.querySelectorAll("input");
let err = false;
for (let input of inputs) {
err = await check(input)
if (err) break;
registrationDATA.set(input.name, input.value);
}
if (err) alert("Не все поля заполнены корректно");
}

// Вызов Registration
try {
await Registration()
catch () {
alert("Ошибка соединения с сервером");
}

asdffak
13.02.2021, 01:02
Еще раз спасибо.
Мне вот стало интересно: а обязательно в последнем варианте функцию Registration объявлять через async?

И еще немного непонятен вот этот момент:

});
.then(function(err) {
if (err) alert("Такой адрес электронной почты уже зарегистрирован");
return err
}
);

В первой же стоке завершилось описание промиса. К чему тогда вызвается метод then? Выглядит как будто к переменной с пустым именем.

voraa
13.02.2021, 08:16
Мне вот стало интересно: а обязательно в последнем варианте функцию Registration объявлять через async?

Да. await можно использовать только в async функциях. По синтактическим правилам только в них await является ключевым словом.
Это check можно было без async объявить, т.к она сама по себе возвращает промис.

И еще немного непонятен вот этот момент:
Можно было и не делать так. Просто сделал, что бы именно тут выдать сообщение, что такой адрес уже есть.
Просто дальше уже невозможно будет понять из-за какого поля err стал true.