Удерживать обещание разрешить / отклонить ссылку на функцию в ожидании ввода - PullRequest
0 голосов
/ 15 февраля 2020

Я хочу использовать обещание для обработки модального окна, чтобы при вызове модального окна с помощью синтаксиса await синхронное выполнение вызывающей функции приостанавливалось до тех пор, пока пользователь не ответит на модальное окно. Приведенный ниже фрагмент кода извлекает основные элементы проблемы. Хотя он работает, я не уверен, является ли это обещанным антипаттерном или я представляю скрытые сложности, если в обработчиках onclick возникают ошибки. Ближайшие вопросы и ответы, которые я смог найти ( Разрешить обещание в более позднее время ), не совсем отвечают моей проблеме, так как ответы, похоже, не относятся к обещанию, которое хранится в резерве в ожидании события пользователя ...

Мой урезанный Modal класс и пример выполнения включают в себя следующие ключевые элементы ...

  • class Modal создает модальные элементы DOM и добавляет их к HTML документ.
  • класс Modal имеет метод с именем show, который показывает модальное значение (в этом упрощенном примере три кнопки) и устанавливает обещание. Функции resolve и reject обещания затем сохраняются как атрибуты Modal экземпляра, в частности resolveFunction и rejectFunction.
  • Только когда пользователь нажимает Okay, Cancel или CancelThrow, Обещание разрешено или отклонено.
  • Функция openModal - это функция, которая устанавливает и показывает модальное состояние, а затем приостанавливает ожидание разрешения обещания, созданного методом модального show().

<html><head>

<style>

#ModalArea {
  display: none;
}

#ModalArea.show {
  display: block;
}

</style>

<script>

class Modal {
  constructor() {

    this.parentNode = document.getElementById( 'ModalArea' );

    let okay = document.createElement( 'BUTTON' );
    okay.innerText = 'Okay';
    okay.onclick = ( event ) => {
      this.resolveFunction( 'Okay button clicked!' )
    };
    this.parentNode.appendChild( okay );
  
    let cancel = document.createElement( 'BUTTON' );
    cancel.innerText = 'Cancel';
    cancel.onclick = ( event ) => {
      this.rejectFunction( 'Cancel button clicked!' )
    };
    this.parentNode.appendChild( cancel );
    
    let cancelThrow = document.createElement( 'BUTTON' );
    cancelThrow.innerText = 'Cancel w/Throw';
    cancelThrow.onclick = ( event ) => {
      try {
        throw 'Thrown error!';
      } catch( err ) {
        this.rejectFunction( err );
      }
      this.rejectFunction( 'CancelThrow button clicked!' );
    };
    this.parentNode.appendChild( cancelThrow );
    
  }
  
  async show() {
    this.parentNode.classList.add( 'show' );
    
    // Critical code:
    //
    // Is this appropriate to stash away the resolve and reject functions
    // as attributes to a class object, to be used later?!
    //
    return new Promise( (resolve, reject) => {
      this.resolveFunction = resolve;
      this.rejectFunction = reject;
    });
  }

}

async function openModal() {

  // Create new modal buttons...
  let modal = new Modal();
  
  // Show the buttons, but wait for the promise to resolve...
  try {
    document.getElementById( 'Result' ).innerText += await modal.show();
  } catch( err ) {
    document.getElementById( 'Result' ).innerText += err;
  }
  
  // Now that the promise resolved, append more text to the result.
  document.getElementById( 'Result' ).innerText += ' Done!';
  
}

</script>

</head><body>


<button onclick='openModal()'>Open Modal</button>
<div id='ModalArea'></div>
<div id='Result'>Result: </div>
</body></html>

Есть ли подводные камни в способе обработки функций resolve и reject, и если да, то есть ли лучший шаблон проектирования для обработки этого варианта использования? ?

РЕДАКТИРОВАТЬ

Основываясь на рекомендациях Roamer-1888, я пришел к следующей более чистой реализации отложенного обещания ... (Обратите внимание, что проверка Cancel w/Throw приводит к тому, что консоль показывает ошибку Uncaught (in Promise), но обработка продолжается, как определено ...)

<html><head>

<style>

#ModalArea {
  display: none;
}

#ModalArea.show {
  display: block;
}

</style>

<script>

class Modal {
  constructor() {

    this.parentNode = document.getElementById( 'ModalArea' );

    this.okay = document.createElement( 'BUTTON' );
    this.okay.innerText = 'Okay';
    this.parentNode.appendChild( this.okay );
  
    this.cancel = document.createElement( 'BUTTON' );
    this.cancel.innerText = 'Cancel';
    this.parentNode.appendChild( this.cancel );
    
    this.cancelThrow = document.createElement( 'BUTTON' );
    this.cancelThrow.innerText = 'Cancel w/Throw';
    this.parentNode.appendChild( this.cancelThrow );
    
  }
  
  async show() {
    this.parentNode.classList.add( 'show' );
    
    let modalPromise = new Promise( (resolve, reject) => {
      this.okay.onclick = (event) => {
        resolve( 'Okay' );
      };
      this.cancel.onclick = ( event ) => {
        reject( 'Cancel' );
      };
      this.cancelThrow.onclick = ( event ) => {
        try {
          throw new Error( 'Test of throwing an error!' );
        } catch ( err ) {
          console.log( 'Event caught error' );
          reject( err );
        }
      };
    });
    
    modalPromise.catch( e => {
      console.log( 'Promise catch fired!' );
    } );
    
    // Clear out the 'modal' buttons after the promise completes.
    modalPromise.finally( () => {
      this.parentNode.innerHTML = '';
    });

    return modalPromise;
  }

}

async function openModal() {

  // Create new modal buttons...
  let modal = new Modal();
  document.getElementById( 'Result' ).innerHTML =  'Result: ';
  
  // Show the buttons, but wait for the promise to resolve...
  try {
    document.getElementById( 'Result' ).innerText += await modal.show();
  } catch( err ) {
    document.getElementById( 'Result' ).innerText += err;
  }
  
  // Now that the promise resolved, append more text to the result.
  document.getElementById( 'Result' ).innerText += ' Done!';  
}

</script>

</head><body>


<button onclick='openModal()'>Open Modal</button>
<div id='ModalArea'></div>
<div id='Result'></div>
</body></html>

Что-то все еще кажется отключенным. После добавления обещания catch при выборе Cancel w/Throw ошибка распространяется через modalPromise.catch, но консоль по-прежнему регистрирует следующую ошибку:

Uncaught (in promise) Error: Test of throwing an error! at HTMLButtonElement.cancelThrow.onclick

1 Ответ

0 голосов
/ 16 февраля 2020

FWIW ... Изучив обработку ошибок в Promises (https://javascript.info/async-await#error -обработка и https://javascript.info/promise-error-handling) и много экспериментировав, я пришел к выводу (возможно, ошибочно), что deferred Promise reject приведет к ошибке Uncaught (in promise).

  • То есть ошибка javascript, несмотря на то, что она была поймана и обработана в ходе выполнения отложенного Promise , завершенный Обещанием reject, будет отображаться как ошибка Uncaught (in promise) в консоли (даже с ошибкой, заключенной в try..catch как в отложенном Обещании, так и в вызывающей функции, которая создала Обещание!).
  • Если, однако, ошибка javascript обнаружена и обработана в ходе выполнения отложенного Обещания и завершена Обещанием resolve, то возникает ошибка no Uncaught (in promise).

В следующем коде рассматриваются варианты разрешения / отклонения отложенного обещания. (Консоль должна быть открыта, чтобы увидеть последовательность обработки и ошибку Uncaught (in promise).)

  • Нет ошибки (в отложенном обещании), заканчивающейся обещанием resolve.
  • Нет ошибок при заключении с Обещанием reject. (Вызывает неперехваченную ошибку)
  • Пойманная ошибка, завершающаяся Обещанием resolve.
  • Пойманная ошибка, завершающаяся Обещанием reject. (Запускает ошибку Uncaught)

Обратите внимание, что даже исходящий вызов openModal () использует функцию Promise catch, в дополнение к переносу с try..catch, но это все равно не перехватывает reject.

<html><head>

<style>
#ModalArea { display: none; }
#ModalArea.show { display: block; }
</style>

<script>

class Modal {
  constructor() {
    this.parentNode = document.getElementById( 'ModalArea' );

    this.okay = document.createElement( 'BUTTON' );
    this.okay.innerText = 'Okay - Promise RESOLVE';
    this.parentNode.appendChild( this.okay );
  
    this.cancel = document.createElement( 'BUTTON' );
    this.cancel.innerText = 'Cancel - Promise REJECT';
    this.parentNode.appendChild( this.cancel );
    
    this.cancelThrow1 = document.createElement( 'BUTTON' );
    this.cancelThrow1.innerText = 'Cancel w/Throw - Promise RESOLVE';
    this.parentNode.appendChild( this.cancelThrow1 );
    
    this.cancelThrow2 = document.createElement( 'BUTTON' );
    this.cancelThrow2.innerText = 'Cancel w/Throw - Promise REJECT';
    this.parentNode.appendChild( this.cancelThrow2 );
  }
  
  async show() {
    this.parentNode.classList.add( 'show' );
    
    let modalPromise = new Promise( (resolve, reject) => {
      this.okay.onclick = (event) => {
        resolve( 'Okay via Promise RESOLVE' );
      };
      this.cancel.onclick = ( event ) => {
        reject( 'Cancel via Promise REJECT' );
      };
      this.cancelThrow1.onclick = ( event ) => {
        try {
          throw new Error( 'Throw /catch error concluding with Promise RESOLVE' );
        } catch ( err ) {
          console.log( 'Cancel w/Throw via Promise RESOLVE' );
          resolve( err );
        }
      };
      this.cancelThrow2.onclick = ( event ) => {
        try {
          throw new Error( 'Throw /catch error concluding with Promise resolve' );
        } catch ( err ) {
          console.log( 'Cancel w/Throw via Promise REJECT' );
          reject( err );
        }
      };
    });
    
    modalPromise.catch( e => {
      console.log( 'Promise CATCH fired!' );
    } );
    
    // Clear out the 'modal' buttons after the promise completes.
    modalPromise.finally( () => {
      console.log( 'Promise FINALLY fired!' );
      this.parentNode.innerHTML = '';
    });

    return modalPromise;
  }

}

async function openModal() {

  // Create new modal buttons...
  let modal = new Modal();
  document.getElementById( 'Result' ).innerHTML =  'Result: ';
  
  // Show the buttons, but wait for the promise to resolve...
  try {
    document.getElementById( 'Result' ).innerText += await modal.show();
  } catch( err ) {
    document.getElementById( 'Result' ).innerText += err;
  }
  
  // Now that the promise resolved, append more text to the result.
  document.getElementById( 'Result' ).innerText += ' - Done!';  
}

</script>

</head><body>


<button onclick="
try {
  openModal()
  .then( x => console.log( 'openModal().THEN fired!' ) )
  .catch( x => console.log( 'openModal().CATCH fired!' ) );
} catch( err ) {
  console.log( [ 'openModal() TRY/CATCH fired!', err ] );
}
">Open Modal</button>
<div id='ModalArea'></div>
<div id='Result'></div>
</body></html>

Итак, я заключаю (опять, возможно, ошибочно), что отложенное Обещание должно быть заключено только с resolve, если желание чтобы избежать ошибки Uncaught (in promise). Другой очевидный вариант - включить обработчик событий unhandledrejection, но это только добавляет ненужной сложности, если можно избежать обещания reject ...

...