4

Интересует на примере кода допущенные ошибки использования Promise. Почему getFiles выполняется раньше auth и как сделать ожидание ответа авторизации и только потом выполнение getFiles?

    var StorageCloud = {};
StorageCloud.API = function(parameters) {
    this.url = parameters['url'] || null;
    this.login = parameters['login'] || null;
    this.password = parameters['password'] || null;
    this.AuthToken = parameters['AuthToken'] || null;
var ajaxGetAuthToken = async function(url, login, password) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url: url,
            type: 'POST',
            timeout: 30000,
            dataType: 'json',
            data: {
                Module: 'Core',
                Method: 'Login',
                Parameters: '{"Login": "' + login + '", "Password" : "' + password + '"}'
            },
            success: (response) => {
                resolve(response);
            },
            error: (response) => {
                reject(response);
            }
        })
    })
}

var ajaxGetFiles = async function(url, token) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url: url,
            type: 'POST',
            async: true,
            dataType: 'json',
            headers: {
                'Authorization': 'Bearer ' + token
            },
            data: {
                Module: 'Files',
                Method: 'GetFiles',
                Parameters: '{"Type" : "personal", "Path": "", "Pattern": ""}'
            },
            success: (response) => {
                resolve(response);
            },
            error: (response) => {
                reject(response);
            }
        });
    })
}

this.getFiles = function(parameters) {
    (async () => {
        return await ajaxGetFiles(this.url, this.AuthToken);
    })();
}

this.auth = function(parameters) {
    if (this.AuthToken == null) {
        this.onSuccesAuth = parameters['onSuccesAuth'] || function() {};
        this.onFailedAuth = parameters['onFailedAuth'] || function() {};

        (async () => {
            this.response = await ajaxGetAuthToken(this.url, this.login, this.password);
            if (this.response.hasOwnProperty('ErrorCode')) {
                this.onFailedAuth();
            } else {
                this.AuthToken = this.response.Result.AuthToken;
                this.onSuccesAuth();
            }
        })();
    }
}

};

var StorageCloud = new StorageCloud.API({ url: "http://file/index.php?/Api/", login: "root", password: "root" });

StorageCloud.auth({ onSuccesAuth: function() { console.log(this.AuthToken); }, onFailedAuth: function() { console.log(this.response.ErrorCode); } });

console.log(StorageCloud.getFiles());

aliokero
  • 736
  • зачем промисы создавать в асинхронных функциях? – DiD Oct 18 '20 at 16:54
  • @DiD нашел в сети пример, в частности для доступа к внешним переменным, в данном случае AuthToken, для callback, в коде onSuccesAuth. Понимаю сама структура ужасна, хотелось бы понять, как должно быть... – aliokero Oct 18 '20 at 16:58
  • Если честно, на ваш вопрос нельзя ответить однозначно, вы используете странные паттерны, которые там не нужны искусственно усложняя код. – DiD Oct 18 '20 at 17:03
  • Мои ответ немного изменился. У вас есть вопросы по коду? – DiD Oct 18 '20 at 17:23
  • У вас auth ничего не возвращает, поэтому дождаться его окончания не получится – vp_arth Oct 18 '20 at 17:34
  • 1
    Связанный вопрос: https://ru.stackoverflow.com/q/554290/176064 – vp_arth Oct 18 '20 at 22:17

2 Answers2

4

Уберите слова async из описаний функций ajaxGetAuthToken и ajaxGetFiles, возвращаемые промисы уже асинхронные.

И вот этот код уберите:

this.getFiles = function(parameters) {
    (async () => {
        return await ajaxGetFiles(this.url, this.AuthToken);
    })();
}

Он лишний, вызывайте сразу ajaxGetFiles.

Вообще, ваш код очень странный. Вы хотите соединить сразу четыре подхода: подход с асинхронные функциями, подход с промисами и callback-driven-подход и как соус к этому венегрету добавили ООП-парадигмы.

Ваш класс должен был быть построен приблизительно так:

class API {
   static get defaults(){
      return {
         url: '',
         login:'',
         password:''
      }
   }
   constructor(params){
      Object.assign(this, API.defaults, params);
   }
   doAuth(){
      return $.ajax({
            url: url,
            type: 'POST',
            timeout: 30000,
            dataType: 'json',
            data: JSON.stringify({
                Module: 'Core',
                Method: 'Login',
                Parameters: JSON.stringify({
                   Login: this.login, 
                   Password: this.password
                })
            }),
            success: (response) => {
                if(response.responseText == 'OK'){
                   resolve(response);
                }
                else {
                   reject('Wrong Login or Password');
                }
            },
            error: (response) => {
                reject(response);
            }
        });
   }
   getFiles(){
      return $.ajax({
            url: url,
            type: 'POST',
            timeout: 30000,
            dataType: 'json',
            headers: {
                Authorization: 'Bearer ' + token
            },
            data: JSON.stringify({
                Module: 'Files',
                Method: 'GetFiles',
                Parameters: JSON.stringify({
                   Type: "personal", 
                   Path: "", 
                   Pattern: ""
                })
            }),
            success: (response) => {
                resolve(response.responseText);
            },
            error: (response) => {
                reject(response);
            }
        });
   }
}

Всё.

А пользоваться этим классом можно так:

(async () => {
   const api = new API({url: '', login:''});
   const files;
   try { 
      await api.doAuth();
      files = await api.getFiles();
   } catch(e){
      console.error(e);
   } finally {
      console.log(files);
   }
})();

И вроде понятно всё и лаконично. А у вас как?

PS. В коде могут быть опечатки.

DiD
  • 7,606
4

jQuery.ajax реализует интерфейс thenable, поэтому всё проще:

fiddle

async function auth() {
    return await $.ajax('https://httpbin.org/get?auth');
}
/*
Что, с точки зрения вызывающего кода, идентично следующему:
function auth() {
    return $.ajax('https://httpbin.org/get?auth');
}
*/
async function getFiles() {
    const files = await $.ajax('https://httpbin.org/get?getFiles&a=1&a=2');
return files.args.a;  

}

auth() .then(getFiles) .then(files => console.log(files))


Асинхронная функция - это функция, которая возвращает Promise.
Можно работать с ним через await в другой асинхронной функции или напрямую, через then.
Важно одно - не нужно терять этот Promise, верните его в вызывающую функцию. Когда вы пишете что-то вроде:

(async () => {
   const a = await ...;
   ...
}())  

вы порождаете Promise, который нигде не используете. Сохраните его, или верните:

return (async () => {
   const a = await ...;
   ...
}())  

Разумеется, сегодня $.ajax не нужен, в современных браузерах есть FetchApi

vp_arth
  • 27,179
  • fetchApi не все сценарии покрывает пока – Grundy Oct 18 '20 at 18:50
  • @Grundy, так нельзя. Мне теперь самому искать эти сценарии? Давай пиши подробнее) – vp_arth Oct 18 '20 at 18:51
  • onprogress для upload :) – Grundy Oct 18 '20 at 18:52
  • Ну, это надо или нотификации в промисы протаскивать, или на Observables переходить) – vp_arth Oct 18 '20 at 18:57
  • @Grundy, кстати, https://www.chromestatus.com/feature/5274139738767360 Ничего не понятно, но вроде уже что-то в Chrome 85 выкатили) – vp_arth Oct 18 '20 at 19:44
  • ага :) они сначала для download похожее добавили, а теперь судя по всему для upload :) непонятно на сколько это где поддерживается сейчас :-) – Grundy Oct 18 '20 at 19:54