2013-12-06 2 views
4

Я использую библиотеку Q в nodejs и не слишком много работал с обещаниями в прошлом, но у меня есть полупростая логика, требующая много гнездования, и мысль Q будет хорошим решением , однако я нахожу, что он кажется почти таким же, как просто «callback hell».Использование Q/обещаний против обратных вызовов

В основном я говорю 5 методов, все из которых требуют данных из предыдущего или одного из предыдущих. Вот пример:

Начнем с некоторых двоичных данных, которые имеют хэш-файл sha1, сгенерированный на основе двоичного кода.

var data = { 
    hash : "XXX" 
    , binary: '' 
} 

Во-первых, мы хотим, чтобы увидеть, если у нас уже есть это, используя этот метод:

findItemByHash(hash)

Если мы не имеем, мы должны сохранить его, используя:

saveItem(hash)

Теперь нам нужно связать это с пользователем, но не только с результатами сохранения. Там сейчас гораздо больше иерархии, которую мы связываем, поэтому нам нужно получить, что первый, делая:

getItemHierarchy(item_id), мы используем item_id вернулся из наших предыдущих saveItem

Теперь мы можем «копировать» эти результаты к пользователь:

saveUserHierarchy(hierarchy)

Теперь мы сделали, однако, это предполагает, что товар еще не существовало. Поэтому нам нужно обработать случай, когда элемент действительно существует. Это было бы:

Нам нужно проверить, если пользователь может aleady иметь это:

getUserItemByItemId(item_id) - item_id был возвращен из findItemByHash

Если он существует, мы сделали.

Если нет:

getItemHierarchy(item_id)

Тогда

saveUserHierarchy(hierarchy)

Итак, теперь у нас есть обратные вызовы, которые делают эти проверки, и это хорошо. Но нам нужно обрабатывать ошибки в каждом случае на этом пути. Это тоже хорошо, просто добавляет в беспорядок. Действительно, если какая-либо часть потока выдает ошибку или отклоняет, то она может остановиться и просто обрабатывать ее в одном месте.

Теперь с Q, мы могли бы сделать что-то вроде этого:

findItemByHash(hash).then(function(res) { 

    if (!res) { 

    return saveItem(hash).then(function(item) { 
     return getItemHierarchy(item.id).then(function(hierarchy) { 
      return saveUserHierarchy(hierarchy); 
     }); 
    }) 

    } else { 

     return getUserItemByItemId(res.id).then(function(user_item) { 

     if (user_item) { 
      return user_item; 
     } 

     return getItemHierarchy(res.id).then(function(hierarchy) { 
      return saveUserHierarchy(hierarchy); 
     }); 

     }); 

    } 
}) 
//I think this will only handle the reject for findItemByHash? 
.fail(function(err) { 
    console.log(err); 
}) 
.done(); 

Так что, я думаю, мой вопрос заключается в следующем. Есть ли лучшие способы справиться с этим в Q?

Спасибо!

+0

В приведенном коде есть возможная ошибка. Ваша вторая 'getItemHierarchy' использует' item.id' вместо 'user_item.id'. Работая над ответом - просто подумал, что я укажу, что в то же время ;-) –

+0

Ах, на самом деле это должно быть использование 'res.id' – dzm

ответ

4

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

В качестве быстрого примера иногда я хочу обрабатывать ошибки и возвращать что-то другое вместо того, чтобы проходить по ошибке. Я буду делать что-то вроде этого:

function awesomeFunction() { 
    var fooPromise = getFoo().then(function() { 
     return 'foo'; 
    }).fail(function(reason) { 
     // handle the error HERE, return the string 'bar' 
     return 'bar'; 
    }); 

    return fooPromise; 
} 

awesomeFunction().then(function(result) { 
    // `result` will either be "foo" or "bar" depending on if the `getFoo()` 
    // call was successful or not inside of `awesomeFunction()` 
}) 
.fail(function(reason) { 
    // This will never be called even if the `getFoo()` function fails 
    // because we've handled it above. 
}); 

Теперь как для вашего вопроса о выходе из «возвращения ада» - до тех пор, как следующая функция не требует информации о предыдущей, вы можете цепи .then статей вместо того, чтобы гнездиться их:

doThis().then(function(foo) { 
    return thenThis(foo.id).then(function(bar) { 
     // `thenThat()` doesn't need to know anything about the variable 
     // `foo` - it only cares about `bar` meaning we can unnest it. 
     return thenThat(bar.id); 
    }); 
}); 

// same as the above 
doThis().then(function(foo) { 
    return thenThis(foo.id); 
}).then(function(bar) { 
    return thenThat(bar.id); 
}); 

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

function getItemHierarchyAndSave(item) { 
    return getItemHierarchy(item.id).then(function(hierarchy) { 
     return saveUserHierarchy(hierarchy); 
    }); 
} 

findItemByHash(hash).then(function(resItem) { 
    if (!resItem) { 
     return saveItem(hash).then(function(savedItem) { 
      return getItemHierarchyAndSave(savedItem); 
     }); 
    } 

    return getUserItemByItemId(resItem.id).then(function(userItem) { 
     return userItem || getItemHierarchyAndSave(resItem); 
    }); 
}) 
.fail(function(err) { console.log(err); }) 
.done(); 

Отказ от ответственности: я не использую обещания Q, я беру в руки when promises прежде всего за дополнительные лакомства, с которыми он поставляется, но принципы одинаковы.

+0

Спасибо большое Тревор, это действительно помогает! Я также проверю библиотеку. – dzm

+0

Итак, если я правильно прочитал это, обработка ошибок делает его похожим на исключения, где вы можете обрабатывать внешний слой, а не принудительно обрабатывать при каждом вызове. – bryanmac

+0

@bryanmac Да, это идеальный способ думать об этом. –

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