The problem I have been struggling with for two weeks now while learning node.js is how to do synchronous programming using node. I found that no matter how I try to do thin
No, one of the great advantages of Promises is that you you can keep your async code linear rather than nested (callback hell from continuation passing style).
Promises give you return statements and error throwing, which you lose with continuation passing style.
You need to return the promise from your async functions so you can chain on the returned value.
Here's an example:
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
takes an array of promises and resolves once all promises resolve, if any are rejected, the array is rejected. This allows you to execute async code concurrently rather than serially, and still wait for the result of all concurrent functions. If you're comfortable with a threaded model, think spawning threads and then joining.
Example:
addEmployeeForm.insertUserName(employee.username)
.then(function() {
// these two functions will be invoked immediately and resolve concurrently
return Promise.all([
addEmployeeForm.insertFirstName(employee.firstName),
addEmployeeForm.insertLastName(employee.lastName)
])
})
// this will be invoked after both insertFirstName and insertLastName have succeeded
.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()
and Promise.reject()
are methods used when creating a Promise
. They're used to wrap an async function using callbacks so that you can work with Promises instead of callbacks.
Resolve will resolve/fulfill the promise (this means a chained then
method will be called with the resulting value).
Reject will reject the promise (this means any chained then
method(s) will not be called, but the first chained catch
method will be called with the error that arose).
I left your setTimeout
in place to preserve your programs behavior, but it's likely unnecessary.
You can chain promises like this:
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);
});
});
});
I just answered a similar question where I explained a technique that uses generators to flatten Promise chains in a nice way. The technique gets its inspiration from coroutines.
Take this bit of code
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();
};
Using it, you can transform your deeply-nested Promise chain into this
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);
}());
Using named generators, we can make that even more legible
// 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());
However, this only scratches the surface of what's possible using coroutines to control the flow of promises. Read the answer I linked above that demonstrates some of the other advantages and capabilities of this technique.
If you do intend to use coroutines for this purpose, I encourage you to check out the co library.
Hope this helps.
PS not sure why you're using setTimeout
in this fashion. What is the point of waiting for 750 ms specifically ?
Yeah like @TateThurston said, we chain them. It's even more aesthetically pleasing when you use es6 arrow functions
I removed the unnecessary nesting. Ill use syntax from 'bluebird'(my preferred Promise library) 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);
I modified your code to include examples for all you questions.
There is no need to use the async library when working with promises. Promises are a very powerful by themselves and I think its an anti-pattern to mix promises and libraries like async.
Normally you should avoid using the var deferred = Promise.pending() style...unless
'when wrapping a callback API that doesn't follow the standard convention. Like setTimeout:'
https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns
For the setTimeout example..create a 'deferred' promise...resolve the promise inside setTimeout and then return the promise outside setTimeout. This might seem a little unintuitive. Look at this example, I answered another question. Q.js promise with node. Missing error handler on `socket`. TypeError: Cannot call method 'then' of undefined
Normally, you can get away with using Promise.promisify(someFunction) to convert a callback type function into a Promise returning function.
Just pass the function calls as an array. Promise.all([promiseReturningCall1, promiseReturningCall2, promiseReturningCall3]);
Your next step is to go from nesting to chaining. You need to realize that each promise is an isolated promise that can be chained in a parent promise. In other words, you can flatten the promises to a chain. Each promise result can be passed to the next one.
Here is a great blog post about it: Flattening Promise Chains. It uses Angular but you can ignore that and look at how a deep nesting of promises turns into a chain.
Another good answer is right here on StackOverflow: Understanding javascript promises; stacks and chaining.