2015-11-23 3 views
0

У меня есть функция, которая должна сделать что-то асинхронное, в несколько шагов. На каждом шагу он может потерпеть неудачу. Он может выйти из строя до первого шага, так что вы можете сразу узнать результат или через 1,5 секунды. Когда не удается, он должен выполнить обратный вызов. Idem когда это удается. (Я использую когда нарочно, потому что это не просто если:. Хронометраж важен)Цепочка асинхронных обещаний с 1 разрешением и 1 отказом?

Я думал Обещание совершенно, так как асинхронные и они решают только один раз, но он все еще имеет проблемы: когда это не удается? Я могу понять, когда это удается (после последнего шага), но когда это не удается? Внутри/перед любым шагом.

Это то, что у меня есть сейчас, но это смешно:

function clickSpeed() { 
    return new Promise(function(resolve, reject) { 
     if (test('one')) { 
      return setTimeout(function() { 
       if (test('two')) { 
        return setTimeout(function() { 
         if (test('three')) { 
          return setTimeout(function() { 
           console.log('resolving...'); 
           resolve(); 
          }, 500); 
         } 
         console.log('rejecting...'); 
         reject(); 
        }, 500); 
       } 
       console.log('rejecting...'); 
       reject(); 
      }, 500); 
     } 
     console.log('rejecting...'); 
     reject(); 
    }); 
} 

(test() случайно проходит или не шаг.)

Fiddle здесь: http://jsfiddle.net/rudiedirkx/zhdrjjx1/

Я предполагаю, что решение это цепочка обещаний, которая разрешает или отвергает каждый шаг.? Может быть. Это что? Как мне это реализовать?

Может ли это работать для неизвестного количества шагов?

+2

Это терпит неудачу, как только вы позволите ему потерпеть неудачу. Не уверен, что я понимаю, в чем вопрос. – Bergi

+0

Ожидаемый результат 'return' at' return setTimeout (function() {'? – guest271314

+1

@ guest271314: Я думаю, что' return' существует только для предотвращения 'console.log ('rejecting ...'); reject(); 'от запуска – Bergi

ответ

2

Вы можете переписать решение обещаниям вполне буквально:

function sleep(ms) { 
    return new Promise(function(resolve) { 
     setTimeout(resolve, ms); 
    }); 
} 

function clickSpeed() { 
    if (test('one')) { 
     return sleep(500).then(function() { 
      if (test('two')) { 
       return sleep(500).then(function() { 
        if (test('three')) { 
         return sleep(500).then(function() { 
          console.log('resolving...'); 
         }); 
        } 
        console.log('rejecting...'); 
        return Promise.reject(); 
       }); 
      } 
      console.log('rejecting...'); 
      return Promise.reject(); 
     }); 
    } 
    console.log('rejecting...'); 
    return Promise.reject(); 
} 

Однако, это все еще довольно некрасиво. Я предпочел бы поменять местами if/else S в качестве первого шага:

function clickSpeed() { 
    if (!test('one')) { 
     console.log('rejecting...'); 
     return Promise.reject(); 
    } 
    return sleep(500).then(function() { 
     if (!test('two')) { 
      console.log('rejecting...'); 
      return Promise.reject(); 
     } 
     return sleep(500).then(function() { 
      if (!test('three')) { 
       console.log('rejecting...'); 
       return Promise.reject(); 
      } 
      return sleep(500).then(function() { 
       console.log('resolving...'); 
      }); 
     }); 
    }); 
} 

Но тогда мы также can unnest эти обратные вызовы. Обычно это невозможно, если вы делаете ветвление с if, но в этом случае единственным альтернативным результатом является отказ, который похож на throw ing и не будет выполнять цепные обратные вызовы then.

function clickSpeed() { 
    return Promise.resolve() // only so that the callbacks look alike, and to use throw 
    .then(function() { 
     if (!test('one')) { 
      console.log('rejecting...'); 
      throw; 
     } 
     return sleep(500); 
    }).then(function() { 
     if (!test('two')) { 
      console.log('rejecting...'); 
      throw; 
     } 
     return sleep(500) 
    }).then(function() { 
     if (!test('three')) { 
      console.log('rejecting...'); 
      throw; 
     } 
     return sleep(500); 
    }).then(function() { 
     console.log('resolving...'); 
    }); 
} 

Теперь вы можете сделать эти test s бросить сам исключение, так что вам не нужно if больше, и вы могли бы переместить console.log('rejecting...'); заявление в .catch().

И в то время как я предполагаю, что это только ваш пример, где все шаги выглядят одинаково, вы можете легко создать обещание цепи динамически из списка, а также:

function clickSpeed() { 
    return ['one', 'two', 'three'].reduce(function(p, cur) { 
     return p.then(function() { 
      if (!test(cur)) 
       throw new Error('rejecting...'); 
      else 
       return sleep(500); 
     }); 
    }, Promise.resolve()); 
} 
+0

Может описать эффект «броска» на столбе? – guest271314

+0

Вот что я играл с, вроде, но не мог понять: http://jsfiddle.net/rudiedirkx/zhdrjjx1/1/ Это больше похоже на это! – Rudie

+1

@ guest271314: 'throw;' не отличается от 'return Promise.r eject(); 'когда внутри обратного вызова' then'. – Bergi

1

Во-первых, давайте переставить вступительную очень тонко, чтобы отразить более типичное и более подходящее использование обещаний.

Вместо:

функция, которая должна сделать то асинхр, в нескольких шагах

скажем:

функция, которая имеет отношение к что-то в нескольких шагах асинхронного действия

Таким образом, мы могли бы сначала решили написать функцию, которая выполняет обещание цепь и возвращает его результирующее обещание:

function doAnAsyncSequence() { 
    return Promise.resolve() 
    .then(function() { 
     doSomethingAsync('one'); 
    }) 
    .then(function() { 
     doSomethingAsync('two'); 
    }) 
    .then(function() { 
     doSomethingAsync('three'); 
    }); 
} 

И, ради демонстрации, мы можем написать doSomethingAsync() таким образом, что он возвращает обещание что есть 50:50 шанс быть решен: отклоненный (который является более полезным здесь, чем задержка):

function doSomethingAsync(x) { 
    return new Promise(function(resolve, reject) { 
     if(Math.random() > 0.5) { 
      resolve(x); 
     } else { 
      reject(x); // importantly, this statement reports the input argument `x` as the reason for failure, which can be read and acted on where doSomethingAsync() is called. 
     } 
    }); 
} 

Затем, центральная часть вопроса:

если это не удается?

может быть перефразировать:

когда не удалась?

который является более реалистичным вопрос, потому что мы, как правило, называть асинхронный процесс (ы), по которому мы имеем мало влияния (они могут работать на каком-то сервере где-нибудь еще в мире), и которые, как мы надеемся добиться успеха, но может случайно потерпеть неудачу. Если это так, наш код (и/или наши конечные пользователи) хотел бы знать, какой из них был неудачным, и почему.

В случае doAnAsyncSequence(), мы можем сделать это следующим образом:

doAnAsyncSequence().then(function(result) { 
    console.log(result); // if this line executes, it will always log "three", the result of the *last* step in the async sequence. 
}, function(reason) { 
    console.log('error: ' + reason); 
}); 

Хотя нет console.log() заявление в любом doAnAsyncSequence() или doSomethingAsync():

  • на успех, мы можем наблюдать в целом результат (который в этом примере всегда будет «три»).
  • по ошибке, мы точно знаем, какой из шагов асинхронизации вызвал сбой процесса («один», «два» или «три»).

Try it out


Так что это теория.

Чтобы ответить на конкретный вопрос (как я его понимаю) ...

для doSomethingAsync(), написать:

function test_(value, delay) { 
    return new Promise(function(resolve, reject) { 
     //call your own test() function immediately 
     var result = test(value); 
     // resolve/reject the returned promise after a delay 
     setTimeout(function() { 
      result ? resolve() : reject(value); 
     }, delay); 
    }); 
} 

для doAnAsyncSequence(), написать:

function clickSpeed() { 
    var delayAfterTest = 500; 
    return Promise.resolve() 
    .then(function() { 
     test_('one', delayAfterTest); 
    }) 
    .then(function() { 
     test_('two', delayAfterTest); 
    }) 
    .then(function() { 
     test_('three', delayAfterTest); 
    }); 
} 

И называют следующим образом:

clickSpeed().then(function() { 
    console.log('all tests passed'); 
}, function(reason) { 
    console.log('test sequence failed at: ' + reason); 
}); 
+0

Очень читаемый, но мне нужен тайм-аут, потому что другие вещи делают вещи асинхронно, и мне приходится ждать ~ 500 мс. – Rudie

+0

Как я уже сказал, мой «doSomethingAsync()» предназначен для демонстрации. В вашем коде реального мира вы можете выбрать что-то совершенно другое. –

1

Поскольку ваше время монотонной, и ваша «работа "предопределено, я бы реорганизовал код для использования setInterval() с con текст для отслеживания требуемой «работы» или «шага». На самом деле, вы даже не имеют использовать обещание, когда делать это, хотя вы все еще можете, и если вы хотите дополнительно обработчиков цепи, это хорошая идея:

function clickSpeed(resolve, reject) { 
    var interval = setInterval(function(work) { 
    try { 
     var current = work.shift(); 
     if(!test(current)) { // Do current step's "work" 
     clearInterval(interval); // reject on failure and clear interval 
     console.log('rejecting...', current); 
     reject(); 
     } 
     else if(!work.length) { // If this was the last step 
     clearInterval(interval); // resolve (success!) and clear interval 
     console.log('resolving...'); 
     resolve(); 
     } 
    } 
    catch(ex) { // reject on exceptions as well 
     reject(ex); 
     clearInterval(interval); 
    } 
    }, 500, ['one', 'two', 'three']); // "work" array 
} 

Функция может либо назвать «непосредственно» с обработчиками разрешения/отклонения или используется как аргумент конструктора Promise.

См. Полный пример в модифицированном JSFiddle.


Для решения слишком много шаблоннога комментария Берги, в коде может быть написан более сжато, без протоколирования:

function clickSpeed(resolve, reject) { 
    function done(success, val) { 
     clearInterval(interval); 
     success ? resolve(val) : reject(val); 
    } 

    var interval = setInterval(function(work) { 
     try { 
      if(test(work.shift()) || done(false)) { 
       work.length || done(true); 
      } 
     } 
     catch(ex) { // reject on exceptions as well 
      done(false, ex); 
     } 
    }, 500, ['one', 'two', 'three']); // "work" array 
} 
+0

Эй, это очень умно! Мне это нравится. – Rudie

+0

Я бы рекомендовал избегать 'setInterval' при использовании обещаний, он просто не вписывается. И вы теряете безопасность, и вам нужно вернуться к messing с обратными вызовами. Кроме того, слишком много шаблонов ... Монотонность легко может быть использована в коде обещания так же легко (см. Обновление к моему ответу). – Bergi

+0

@Bergi - Решение на самом деле не использует обещания, но «позволяет» обертывать обещанием. Обработка исключений выпекается внутри, а механизм обратного вызова - по дизайну (как описано в ОП). Бойлер минимален (как показано в обновленной версии) и сопоставим с вашим решением, которое, как я думаю, также является хорошим. – Amit

Смежные вопросы