프로젝트 진행 중에 웹사이트의 로그인과 회원가입 기능을 만들고 있었다. 그 중에 회원가입 API를 작성하던 중에 오류가 발생했다.
회원가입을 위해서 아이디 중복 확인을 하는 API를 먼저 만들어서 중복확인 버튼 클릭시 실행되도록 적용시켰고, 회원가입 폼을 submit
할 때 서버와 통신하기 전에 한번 더 각 항목이 올바르게 작성되었는지 확인하는 함수를 먼저 실행시켰다. 그리고 모든 값이 올바를 때만 서버에 데이터가 전송되도록 API를 설계하였다.
그런데 문제는 아이디 중복확인 API와 다른 항목들이 올바르게 작성되었는지 확인하는 함수들과 서버에 회원가입 폼의 정보들을 전달하는 API의 실행시간이 달라서
발생하였다.
올바른 형식의 아이디를 먼저 입력한 후 아이디 중복확인을 통해 올바른 아이디라고 값이 반환된 후에 아이디를 잘못된 것으로 변경하고 중복확인 버튼을 누르지 않았다고 해보자. 그리고 다른 항목들을 올바르게 입력한 후 submit
버튼을 누른다면 아이디 중복확인 API가 새로 입력한 아이디가 잘못되었다고 말해주기도 전에 다른 모든 함수들의 실행이 끝나게 되어 모두 올바른 입력이어야 실행되는 회원정보 전달 API가 실행되는 것이다.
원래는 새로 입력한 아이디가 잘못되었으니 오류를 알리고 서버에 정보전달이 되어서는 안되도록 설계한 것인데 ajax
의 비동기적 특성이 이런 오류를 발생하게 만들었다.
내가 원하는 것은 아이디 중복확인 함수가 실행이 끝나고 중복검사의 결과가 나온 후에 회원 정보전달 API가 실행되는 것이다.
이를 위해서는 ajax 통신이 비동기적이 아니라 동기적으로 실행되도록 해야한다. 이를 위해서 구글링을 해보니 async: False 라는 옵션을 사용하면 동기적으로 작동되도록 만들 수 있다.
고민을 오래하기도 하고 수많은 방법들을 찾아봤는데, 사실 너무 간단하게 해결하게 되어서 뭔가 씁쓸했다.
// 아이디 중복 확인
const userIdConfirm = document.querySelector('#user_id_confirm_button')
userIdConfirm.addEventListener('click', user_id_confirm)
// 동기 처리
function user_id_confirm() {
console.log('# 아이디 확인 작업 시작')
$('#user_id').removeClass('is-invalid is-valid');
$('#id-feedback').remove()
let user_id = $("#user_id").val();
console.log('id: ', user_id)
typeof (user_id)
if (user_id.includes(' ') || user_id.includes(',') === true) {
$('#user_id').addClass('is-invalid');
$('#user_id_confirm_button').after(`<div id="id-feedback" class="invalid-feedback" style="text-align: right">아이디에 "공백"이나 ","가 포함되어서는 안됩니다 😭</div>`)
console.log('아이디에 공백 및 쉼표 포함되어 실패')
return
}
$.ajax({
type: "POST",
url: "/main/signup/userid",
data: {
user_id_give: user_id
},
async: false,
success: function (response) {
console.log('response[result]:', response["result"]);
let result = response["result"];
if (result === 'available') {
$('#user_id').addClass('is-valid');
$('#user_id_confirm_button').after(`<div id="id-feedback" class="valid-feedback" style="text-align: right">사용가능한 아이디입니다 😝</div>`)
console.log('사용가능한 아이디')
} else {
$('#user_id').addClass('is-invalid');
$('#user_id_confirm_button').after(`<div id="id-feedback" class="invalid-feedback" style="text-align: right">다른 아이디를 선택해주세요 😭</div>`)
console.log('중복된 아이디')
}
},
});
}
// 비밀번호 글자 수 확인
const pw = document.querySelector('#user_pw')
pw.addEventListener('focusout', user_pw_length_confirm)
function user_pw_length_confirm() {
console.log('비밀번호 글자수 확인 작업 시작')
$('#user_pw').removeClass('is-invalid is-valid');
$('#pw-length-feedback').remove()
let user_pw = $("#user_pw").val();
console.log('1번째 칸 비밀번호: ', user_pw)
console.log('비밀번호 글자 수: ', user_pw.length)
if (user_pw.includes(' ') || user_pw.includes(',') === true) {
$('#user_pw').addClass('is-invalid');
$('#user_pw').after(`<div id="pw-length-feedback" class="invalid-feedback" style="text-align: right">비밀번호에 "공백"이나 ","가 포함되어서는 안됩니다 😭</div>`)
console.log('비밀번호에 공백 및 쉼표 포함되어 실패')
return false
} else if (user_pw.length >= 4) {
$('#user_pw').addClass('is-valid');
$('#user_pw').after(`<div id="pw-length-feedback" class="valid-feedback" style="text-align: right">사용가능한 비밀번호입니다 😝</div>`)
console.log('비밀번호 글자수 조건만족')
return true
} else {
$('#user_pw').addClass('is-invalid');
$('#user_pw').after(`<div id="pw-length-feedback" class="invalid-feedback" style="text-align: right">비밀번호의 글자수를 확인해주세요 😭</div>`)
console.log('비밀번호 글자수 조건불만족')
return false
}
}
// 비밀번호랑 비밀번호 확인이랑 같은지 확인
const pwConfirm = document.querySelector('#user_pw_confirm')
pwConfirm.addEventListener('focusout', user_pw_confirm)
function user_pw_confirm() {
console.log('비밀번호 확인 작업 시작')
$('#user_pw_confirm').removeClass('is-invalid is-valid');
$('#pw-feedback').remove()
console.log('2번째 칸 비밀번호: ', pwConfirm.value)
if (pw.value.length >= 4 && pw.value === pwConfirm.value) {
$('#user_pw_confirm').addClass('is-valid');
$('#user_pw_confirm').after(`<div id="pw-feedback" class="valid-feedback" style="text-align: right">비밀번호가 일치합니다 😝</div>`)
console.log('비밀번호 일치')
return true
} else {
$('#user_pw_confirm').addClass('is-invalid');
$('#user_pw_confirm').after(`<div id="pw-feedback" class="invalid-feedback" style="text-align: right">비밀번호가 다릅니다 😭</div>`)
console.log('비밀번호 불일치')
return false
}
}
// 이름 입력 확인
const name = document.querySelector('#user_name')
name.addEventListener('focusout', user_name_confirm)
function user_name_confirm() {
console.log('이름 확인 작업 시작')
$('#user_name').removeClass('is-invalid is-valid');
$('#name-feedback').remove()
console.log('이름 :', name.value)
if (name.value.includes(' ') || name.value.includes(',') === true) {
name.addClass('is-invalid');
name.after(`<div id="name-feedback" class="invalid-feedback" style="text-align: right">이름에 "공백"이나 ","가 포함되어서는 안됩니다 😭</div>`)
console.log('이름에 공백 및 쉼표 포함되어 실패')
return false
} else if (name.value.length >= 1) {
$('#user_name').addClass('is-valid');
$('#user_name').after(`<div id="name-feedback" class="valid-feedback" style="text-align: right">예쁜 이름이네요 😝</div>`)
console.log('이름 확인 성공')
return true
} else {
$('#user_name').addClass('is-invalid');
$('#user_name').after(`<div id="name-feedback" class="invalid-feedback" style="text-align: right">다른 이름을 입력해주세요 😭</div>`)
console.log('이름 확인 실패')
return false
}
}
// 이메일 입력 및 형식 확인
const email = document.querySelector('#user_email')
email.addEventListener('focusout', user_email_confirm)
function user_email_confirm() {
console.log('이메일 확인 작업 시작')
$('#user_email').removeClass('is-invalid is-valid');
$('#email-feedback').remove()
console.log('이메일 :', email.value)
if (email.value.split('@').length === 2 && email.value.split('@')[1].split('.').length >= 2 && email.value.split('@')[1].split('.').length <= 3) {
$('#user_email').addClass('is-valid');
$('#user_email').after(`<div id="email-feedback" class="valid-feedback" style="text-align: right">멋진 이메일이군요 😝</div>`)
console.log('이메일 확인 성공')
// return true
} else {
$('#user_email').addClass('is-invalid');
$('#user_email').after(`<div id="email-feedback" class="invalid-feedback" style="text-align: right">이메일 형식을 다시 확인해주세요 😭</div>`)
console.log('이메일 확인 실패')
// return false
}
}
// post 요청 전 마지막 form 확인
const signup = document.querySelector('#submit-signup')
signup.addEventListener('click', totalConfirm)
function totalConfirm() {
user_id_confirm()
user_pw_length_confirm()
user_pw_confirm()
user_name_confirm()
user_email_confirm()
console.log('아이디 확인 작업 시작')
if (!$('#user_id').hasClass('is-valid')) {
return alert('아이디가 올바른지 확인해주세요')
}
console.log('비밀번호 글자수 확인 작업 시작')
if (!$('#user_pw').hasClass('is-valid')) {
return alert('비밀번호의 글자 수가 올바른지 확인해주세요')
}
console.log('비밀번호 일치 확인 작업 시작')
if (!$('#user_pw_confirm').hasClass('is-valid')) {
return alert('비밀번호가 일치하는지 확인해주세요')
}
console.log('이름 확인 작업 시작')
if (!$('#user_name').hasClass('is-valid')) {
return alert('이름이 올바른지 확인해주세요')
}
console.log('이메일 확인 작업 시작')
if (!$('#user_email').hasClass('is-valid')) {
return alert('이메일이 올바른지 확인해주세요')
}
sign_up()
}
// post 요청
function sign_up() {
let user_id = $("#user_id").val();
let user_pw = $("#user_pw").val();
let user_name = $("#user_name").val();
let user_email = $("#user_email").val();
console.log(user_id, user_pw, user_name, user_email)
$.ajax({
type: "POST",
url: "/main/signup",
data: {
user_id_give: user_id,
user_name_give: user_name,
user_email_give: user_email,
user_pw_give: user_pw,
},
success: function (response) {
console.log(response["doc"]);
alert('회원가입 성공!')
window.location.reload();
},
});
}
from flask import Flask, render_template, request, jsonify
import pymysql
app = Flask(__name__)
# ID 중복 확인
@app.route("/main/signup/userid", methods=["POST"])
def user_id_confirm():
user_id_receive = request.form['user_id_give']
# mySQL db 접속
db = pymysql.connect(host='121.166.127.220', user='haksoo', db='sparta_sbsj', password='12345678', charset='utf8')
curs = db.cursor()
# 전체 데이터 조회 후 data_list 변수에 할당
sql = "SELECT * FROM sparta_sbsj.`user` u"
curs.execute(sql)
data_list = curs.fetchall()
result = []
for data in data_list:
if user_id_receive == data[1]:
temp = {'user_id': data[1]}
result.append(temp)
break
db.commit()
db.close()
result_msg = 'fail' if len(result) == 1 else 'available'
return jsonify({'result': result_msg})
# 회원 가입시 회원 정보 DB에 입력
@app.route("/main/signup", methods=["POST"])
def sign_up():
user_id_receive = request.form['user_id_give']
user_pw_receive = request.form['user_pw_give']
user_name_receive = request.form['user_name_give']
user_email_receive = request.form['user_email_give']
data_receive = (user_id_receive, user_pw_receive, user_name_receive, user_email_receive)
db = pymysql.connect(host='121.166.127.220', user='haksoo', db='sparta_sbsj', password='12345678', charset='utf8')
curs = db.cursor()
sql = "insert into user (user_id, user_pw, user_name, user_email) values(%s, %s, %s, %s);"
curs.execute(sql, data_receive)
db.commit()
db.close()
return jsonify({'msg': 'data insert 성공!'})
간단한 기능 추가였지만 이번 문제 해결과정을 통해서 동기와 비동기의 차이를 직접적으로 느낄 수 있었고, 클라이언트에서 함수를 실행하는 것과 서버와 통신하는 에이피아이 함수의 실행 속도의 차이가 생각보다 크다는 것을 직접적으로 느낄 수 있어서 좋았던 것 같다.