2016-03-04 2 views
33

Проблема, с которой я боролся в течение двух недель, изучая node.js, - это как сделать синхронное программирование с помощью узла. Я обнаружил, что независимо от того, как я пытаюсь делать все подряд, я всегда получаю вложенные обещания. Я обнаружил, что есть такие модули, как Q, чтобы помочь с цепочкой обещаний в отношении ремонтопригодности. Что-то, чего я не понимаю при проведении исследований, это Promise.all() и Promise.resolve() и Promise.reject(). Promise.reject в значительной степени объясняет себя по имени, но при написании приложения я смущен тем, как включать любые из них в функции или объекты, не нарушая поведение приложения. Существует определенно кривая обучения для node.js при выходе с языка программирования, такого как Java или C#. Вопрос, который все еще существует, заключается в том, что обычная цепочка (лучшая практика) в node.js.Являются ли вложенные обещания нормальными в node.js?

пример

driver.get('https://website.com/login').then(function() { 
loginPage.login('company.admin', 'password').then(function() { 
    var employeePage = new EmployeePage(driver.getDriver()); 

    employeePage.clickAddEmployee().then(function() { 
     setTimeout(function() { 
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); 

      addEmployeeForm.insertUserName(employee.username).then(function() { 
       addEmployeeForm.insertFirstName(employee.firstName).then(function() { 
        addEmployeeForm.insertLastName(employee.lastName).then(function() { 
         addEmployeeForm.clickCreateEmployee().then(function() { 
          employeePage.searchEmployee(employee); 
         }); 
        }); 
       }); 
      }); 
     }, 750); 
    }); 
}); 
}); 
+0

[Вот очень похожий вопрос я задал некоторое время назад] (http://stackoverflow.com/questions/22539815/arent-promises-just-callbacks) –

ответ

51

Нет, одно из величайших преимуществ обещаний заключается в том, что вы можете сохранить свой асинхронный код линейным, а не вложенным (callback hell из стиля продолжения прохождения).

Обещания дают вам заявления о возврате и бросание ошибок, которые вы теряете с продолжением стиля прохода.

В вашем случае вам необходимо вернуть обещание от функций возврата обещаний, чтобы вы могли связать свои обещания.

Вот быстрый рефакторинг иллюстрирующих цепочек:

driver.get('https://website.com/login') 
    .then(function() { 
    return loginPage.login('company.admin', 'password') 
    }) 
    .then(function() { 
    var employeePage = new EmployeePage(driver.getDriver()); 
    return employeePage.clickAddEmployee(); 
    }) 
    .then(function() { 
    setTimeout(function() { 
     var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); 

     addEmployeeForm.insertUserName(employee.username) 
     .then(function() { 
      return addEmployeeForm.insertFirstName(employee.firstName) 
     }) 
     .then(function() { 
      return addEmployeeForm.insertLastName(employee.lastName) 
     }) 
     .then(function() { 
      return addEmployeeForm.clickCreateEmployee() 
     }) 
     .then(function() { 
      return employeePage.searchEmployee(employee); 
     }); 
    }, 750); 
    }); 

Promise.all принимает массив обещаний и решает, как все обещания решить, если таковые будут отклонены, то массив отклоняется.

Пример:

addEmployeeForm.insertUserName(employee.username) 
     .then(function() { 
      //these two functions will execute in parallel 
      return Promise.all([ 
      addEmployeeForm.insertFirstName(employee.firstName), 
      addEmployeeForm.insertLastName(employee.lastName) 
     ]) 
     //this will be called after both insertFirstName and insertLastName have suceeded 
     .then(function() { 
      return addEmployeeForm.clickCreateEmployee() 
     }) 
     .then(function() { 
      return employeePage.searchEmployee(employee); 
     }) 
     //if an error arises anywhere in the chain this function will be invoked 
     .catch(function(err){ 
      console.log(err); 
     }); 

Promise.resolve() и Promise.reject() являются методы, используемые при создании Promise из функции асинхронной. Resolve решит/выполнит обещание (это означает, что вызванный метод будет вызван с результирующим значением). Reject отклонит обещание (это означает, что прикованный тогда метод не будет вызываться, но метод с цепным методом будет вызван с ошибкой что возникло).

Кроме того, почему вы применили функцию SetTimeout?

+0

Спасибо, это мне очень помогло. – Grim

+0

Рад, что я мог бы помочь! –

+0

@TateThurston Прости меня за то, что ты слишком нуб. У меня есть вопрос, выполняются ли последовательные обещания (как в первом примере) последовательно? Итак, скажем, первое обещание заняло 1 сек для завершения асинхронного запроса, второе обещание, прикованное '.then', не будет выполнено до тех пор, пока первый не завершится, это правильно? И это одно и то же для третьего обещания и так далее? – JohnnyQ

8

Используйте async библиотеку и использовать async.series вместо вложенных chainings, который выглядит очень некрасиво и трудно отлаживать/понять.

async.series([ 
    methodOne, 
    methodTwo 
], function (err, results) { 
    // Here, results is the value from each function 
    console.log(results); 
}); 

Promise.all(iterable) метод возвращает обещание, которое решает, когда все обещания в итерации аргумента решили, или отвергающее с причиной первого прошедшим обещания, отвергающим.

var p1 = Promise.resolve(3); 
var p2 = 1337; 
var p3 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "foo"); 
}); 

Promise.all([p1, p2, p3]).then(function(values) { 
    console.log(values); // [3, 1337, "foo"] 
}); 

Promise.resolve(value) Метод возвращает объект Promise, который разрешен с заданным значением. Если значение является допустимым (т. Е. Имеет метод then), возвращенное обещание будет «следовать», что затем, принимая его возможное состояние; в противном случае возвращенное обещание будет выполнено со значением.

var p = Promise.resolve([1,2,3]); 
p.then(function(v) { 
    console.log(v[0]); // 1 
}); 

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

+0

Вы, безусловно, есть чистейший пример использования асинхр. серии и Promise.all. Имеются ли результаты для async.series в точности так же, как и values.all, где это массив значений? – Grim

+0

Также можно сказать, что я использую async.series() в функции внутри объекта, и мне нужно связать обещания внутри этой функции. Вернул бы async.series() или не возвратил бы ничего вызывающему? – Grim

+3

Здесь нет оснований использовать библиотеку 'async', чтобы избежать вложенности. ОП может связывать свои обещания без гнездования. – jfriend00

1

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

Отличное сообщение в блоге об этом: Flattening Promise Chains. Он использует Angular, но вы можете игнорировать это и посмотреть, как глубокое вложение обещаний превращается в цепочку.

Другой хороший ответ прямо здесь, на StackOverflow: Understanding javascript promises; stacks and chaining.

0

Вы можете цепи, как это обещает:

driver.get('https://website.com/login').then(function() { 
    return loginPage.login('company.admin', 'password') 
)}.then(function() { 
    var employeePage = new EmployeePage(driver.getDriver()); 

    return employeePage.clickAddEmployee().then(function() { 
     setTimeout(function() { 
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); 
     return addEmployeeForm.insertUserName(employee.username).then(function() { 
       retun addEmployeeForm.insertFirstName(employee.firstName) 
     }).then(function() { 
       return addEmployeeForm.insertLastName(employee.lastName) 
     }).then(function() { 
      return addEmployeeForm.clickCreateEmployee() 
     }).then(function() { 
      retrun employeePage.searchEmployee(employee); 
     })}, 750); 
}); 

}); });

+0

Меня смущает несколько возвратов. У вас есть .then (function() {return statement}); Когда он используется таким образом, где возвращается возвращаемое значение или в этом случае обещание? При синхронном программировании возврат возвращается к вызывающему абоненту. Что такое вызывающий? – Grim

2

Я только что ответил similar question, где я объяснил технику, которая использует генераторы для сглаживания цепочек Promise красивым способом. Техника получает вдохновение от сопрограмм.

Возьмите этот кусок кода

Promise.prototype.bind = Promise.prototype.then; 

const coro = g => { 
    const next = x => { 
    let {done, value} = g.next(x); 
    return done ? value : value.bind(next); 
    } 
    return next(); 
}; 

Используя его, вы можете превратить ваш глубоко вложенной Promise цепь в этом

coro(function*() { 
    yield driver.get('https://website.com/login') 
    yield loginPage.login('company.admin', 'password'); 
    var employeePage = new EmployeePage(driver.getDriver()); 
    yield employeePage.clickAddEmployee(); 
    setTimeout(() => { 
    coro(function*() { 
     var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); 
     yield addEmployeeForm.insertUserName(employee.username); 
     yield addEmployeeForm.insertFirstName(employee.firstName); 
     yield addEmployeeForm.insertLastName(employee.lastName); 
     yield addEmployeeForm.clickCreateEmployee(); 
     yield employeePage.searchEmployee(employee); 
    }()); 
    }, 750); 
}()); 

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

// don't forget to assign your free variables 
// var driver = ... 
// var loginPage = ... 
// var employeePage = new EmployeePage(driver.getDriver()); 
// var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); 
// var employee = ... 

function* createEmployee() { 
    yield addEmployeeForm.insertUserName(employee.username); 
    yield addEmployeeForm.insertFirstName(employee.firstName); 
    yield addEmployeeForm.insertLastName(employee.lastName); 
    yield addEmployeeForm.clickCreateEmployee(); 
    yield employeePage.searchEmployee(employee); 
} 

function* login() { 
    yield driver.get('https://website.com/login') 
    yield loginPage.login('company.admin', 'password'); 
    yield employeePage.clickAddEmployee(); 
    setTimeout(() => coro(createEmployee()), 750); 
} 

coro(login()); 

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

Если вы намерены использовать сопрограммы для этой цели, я рекомендую вам ознакомиться с co library.

Надеюсь, это поможет.

PS не уверен, почему вы используете setTimeout таким образом. Какой смысл ждать 750 мс?

+0

SetTimeout используется, чтобы дождаться, когда браузер отобразит новую страницу, а затем выполнит эти действия, Node.js действительно быстро, когда делая автоматизацию тестирования. Мне нужно сделать еще несколько исследований, но это действительно хороший ответ и, возможно, лучший ответ. – Grim

+0

@CharlesSexton хорошо должен быть каким-то образом вы можете обернуть обратный вызов/обещание вокруг этого. Я не уверен, какую тестовую библиотеку вы используете, но ожидание произвольной суммы - это глупо. – naomik

+0

Я использовал mocha/Chai, но я просто писал сценарии для практики с обещаниями. Какую библиотеку тестирования вы рекомендуете для тестирования автоматизации? Я думал, что обещания - лучший подход к дизайну, чем обратные вызовы. Я, вероятно, мог бы обернуть его в обратном вызове, но мне нужно будет поиграть с ним, чтобы посмотреть. – Grim

3

Я удалил ненужное гнездование. Ill использовать синтаксис из «Блюберд» (моя любимая библиотека Promise) http://bluebirdjs.com/docs/api-reference.html

var employeePage; 

driver.get('https://website.com/login').then(function() { 
    return loginPage.login('company.admin', 'password'); 
}).then(function() { 
    employeePage = new EmployeePage(driver.getDriver());  
    return employeePage.clickAddEmployee(); 
}).then(function() { 
    var deferred = Promise.pending(); 
    setTimeout(deferred.resolve,750); 
    return deferred.promise; 
}).then(function() { 
    var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); 
    return Promise.all([addEmployeeForm.insertUserName(employee.username), 
         addEmployeeForm.insertFirstName(employee.firstName), 
         addEmployeeForm.insertLastName(employee.lastName)]); 
}).then(function() { 
    return addEmployeeForm.clickCreateEmployee(); 
}).then(function() { 
    return employeePage.searchEmployee(employee); 
}).catch(console.log); 

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

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

  2. Обычно вам не следует использовать стиль var debferred = Promise.pending() ...если не

«, когда упаковка обратного вызова API, который не соответствует стандартной конвенции. Как SetTimeout:»

https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns

Для SetTimeout example..create с 'отложенным' обещание ... разрешить обещание внутри SetTimeout, а затем вернуть обещание вне SetTimeout. Это может показаться немного неинтуитивным. Посмотрите на этот пример, я ответил на другой вопрос. Q.js promise with node. Missing error handler on `socket`. TypeError: Cannot call method 'then' of undefined

Как правило, вы можете уйти с помощью Promise.promisify (someFunction), чтобы преобразовать функцию типа обратного вызова в функцию возврата Promise.

  1. Promise.all Допустим, вы совершаете несколько вызовов службы, которая возвращается асинхронно. Если они не зависят друг от друга, вы можете совершать вызовы одновременно.

Просто передайте вызовы функций в виде массива. Promise.all ([promReturningCall1, promReturningCall2, promReturningCall3]);

  1. Наконец-то добавьте блокирующий блок до самого конца ..., чтобы убедиться, что вы поймаете какую-либо ошибку. Это приведет к любым исключениям в любой точке цепи.
+0

Где вы получаете 'Promise.pending()' from? Это не стандартный метод Promise, о котором я знаю. Почему бы просто не использовать стандартный конструктор обещаний? Кроме того, произвольная задержка, вставленная в этот процесс (который, как я знаю, не была вашей идеей, но пришла из OP), пахнет как хак, который, вероятно, не является правильным способом кодирования этого. – jfriend00

+0

Promise.pending - синтаксис синей птицы. Я согласен, что использование setTimeout выглядит забавным. Я оставил его, чтобы объяснить ручное создание обещаний. –

+0

Тогда два очка. 1) Вы должны сказать в своем ответе, что вы полагаетесь на Bluebird (это тоже моя библиотека обещаний), поскольку она выходит за рамки стандартных обещаний. 2) Если вы используете Bluebird, тогда вы можете использовать 'Promise.delay()' вместо 'Promise.pending()' и 'setTimeout()'. – jfriend00

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