Как «дождаться» обратного звонка?


100

При использовании простого обратного вызова, такого как в примере ниже:

test() {
  api.on( 'someEvent', function( response ) {
    return response;
  });
}

Как можно изменить функцию для использования async / await? В частности, предполагая, что 'someEvent' гарантированно будет вызываться один раз и только один раз, я бы хотел, чтобы функциональный тест был асинхронной функцией, которая не возвращается, пока не будет выполнен обратный вызов, например:

async test() {
  return await api.on( 'someEvent' );
}

1
Для справки: спецификация ES7 / ES2016 завершена и не включает async / await. На данный момент это всего лишь предложение стадии 3 .
Дэн Принс

Что ж, удивительно - очень надеюсь, что он будет включен! Спасибо за информацию @DanPrince
sean2078

Ответы:


146

async/awaitэто не волшебство. Асинхронная функция - это функция, которая может разворачивать Promises за вас, поэтому вам нужно api.on()будет вернуть Promise, чтобы это сработало. Что-то вроде этого:

function apiOn(event) {
  return new Promise(resolve => {
    api.on(event, response => resolve(response));
  });
}

затем

async function test() {
  return await apiOn( 'someEvent' ); // await is actually optional here
                                      // you'd return a Promise either way.
}

Но это тоже ложь, потому что асинхронные функции также возвращают сами обещания, поэтому вы на самом деле не собираетесь получать значение из test(), а, скорее, обещание для значения, которое вы можете использовать следующим образом:

async function whatever() {
  // snip
  const response = await test();
  // use response here
  // snip
}

3
Укороченная версия функции, возвращающей обещание: const apiOn = (event) => new Promise(resolve => api.on(event, resolve));
Фелипе

7

Раздражает то, что нет простого решения, и упаковка return new Promise(...)неуправляема, но я нашел подходящее решение util.promisify(на самом деле она также как бы делает ту же упаковку, только выглядит лучше).

function voidFunction(someArgs, callback) {
  api.onActionwhichTakesTime(someMoreArgs, (response_we_need) => {
    callback(null, response_we_need);
  });
}

Вышеупомянутая функция пока ничего не возвращает. Мы можем сделать это возвращать Promiseиз responseпроходил в callbackвыполнив:

const util = require('util');

const asyncFunction = util.promisify(voidFunction);

Теперь мы можем на самом деле .awaitcallback

async function test() {
  return await asyncFunction(args);
}

Некоторые правила при использовании util.promisify

  • callbackДолжен быть последним аргументом функции , которая собирается бытьpromisify
  • Предполагаемый обратный вызов должен быть в форме (err, res) => {...}

Забавно то, что нам не нужно когда-либо специально писать, что есть на callbackсамом деле.


3

async / await - это волшебство. Вы можете создать функцию asPromiseдля обработки таких ситуаций:

function asPromise(context, callbackFunction, ...args) {
    return new Promise((resolve, reject) => {
        args.push((err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
        if (context) {
            callbackFunction.call(context, ...args);
        } else {
            callbackFunction(...args);
        }
    });
}

а затем используйте его, когда хотите:

async test() {
    return await this.asPromise(this, api.on, 'someEvent');
}

количество аргументов варьируется.


1

Вы можете добиться этого без обратных вызовов, используйте обещание async await вместо обратных вызовов, как я бы это сделал. А также здесь я проиллюстрировал два метода обработки ошибок.

clickMe = async (value) => {
  
  // begin to wait till the message gets here;
  let {message, error} = await getMessage(value);
  
  // if error is not null
  if(error)
    return console.log('error occured ' + error);
   
  return console.log('message ' + message);

}

getMessage = (value) => {

  //returning a promise 
  return new Promise((resolve, reject) => {
  
    setTimeout(() => {
      // if passed value is 1 then it is a success
      if(value == 1){
        resolve({message: "**success**", error: null});
      }else if (value == 2){
        resolve({message: null, error: "**error**"});
      }
    }, 1000);
  
  });

}

clickWithTryCatch = async (value) => {

  try{
    //since promise reject in getMessage2 
    let message = await getMessage2(value);
    console.log('message is ' + message);
  }catch(e){
    //catching rejects from the promise
    console.log('error captured ' + e);
  }

}

getMessage2 = (value) => {

  return new Promise((resolve, reject) => {
  
    setTimeout(() => {
      if(value == 1)
        resolve('**success**');
      else if(value == 2)
        reject('**error**'); 
    }, 1000);
  
  });

}
<input type='button' value='click to trigger for a value' onclick='clickMe(1)' />
<br/>
<input type='button' value='click to trigger an error' onclick='clickMe(2)' />
<br/>
<input type='button' value='handling errors with try catch' onclick='clickWithTryCatch(1)'/>
<br/>
<input type='button' value='handling errors with try catch' onclick='clickWithTryCatch(2)'/>

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.