Async control flow in JavaScript: Promises, Microtasks, async/await

Slides for Henning's talk on Sep 21st 2017.


Understanding sync vs. async control flow

Talking to synchronous (or "blocking") API

print('script start')
html = get('/foo')
print(html)
print('script end')

Script outputs 'script start', (long delay), '<html>...</html>', 'script end'.

Talking to asynchronous (or "evented") API

print('script start')
get('foo', done: function(html) {
  print(html)
})
print('script end')

Script outputs 'script start', `'...

JavaScript: Polyfill native Promise API with jQuery Deferreds

You should prefer native promises to jQuery's Deferreds. Native promises are much faster than their jQuery equivalent.

Native promises are supported on all browsers except IE <=11, Android <= 4.4 and iOS <= 7.

If you need Promise support for these old browsers y...

JavaScript: How to query the state of a Promise

Native promises have no methods to inspect their state.

You can use the promiseState function below to check whether a promise is fulfilled, rejected or still pending:

promiseState(promise, function(state) {
  // `state` now either "pending", "fulfilled" or "rejected"
});

Note that the callback passed to promiseState will be called asynchronously in the next [microtask](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/...

Caution when using the || operator to set defaults

I often see the use of || to set a default value for a variable that might be nil, null or undefined.

x = x || 'default-value'

This pattern should be avoided in all languages.

While using || works as intended when x is null or an actual object, it also sets the default value for other falsy values, such as false. false is a non-blank value that you never want to override with a default.

To make it worse, languages like JavaScript or Perl have [many more fal...

Cucumber: Clear localStorage after each scenario

Capybara clears cookies before each scenario, but not other client-side data stores. If your app is using localStorage or sessionStorage, contents will bleed into the next scenario.

Use this hook to remove all site data after each scenario:

After do
  if Capybara.current_driver == :selenium && !Capybara.current_url.starts_with?('data:')
    page.execute_script <<-JAVASCRIPT
      localStorage.clear();
      sessionStorage.clear();
    JAVASCRIPT
  end
end

How to make select2 use a Bootstrap 4 theme

select2 is a great jQuery library to make (large) <select> fields more usable.

For Bootstrap 3 there is select2-bootstrap-theme.
It won't work for Bootstrap 4, but rchavik's fork does (at least with Bootstrap 4 Alpha 6, current version at time of writing this).

It is based on a solution by angel-vladov and adds a few fixes.

Use it by addi...

How to make Webpacker compile once for parallel tests, and only if necessary

Webpack is the future. We're using it in our latest Rails applications.

For tests, we want to compile assets like for production.
For parallel tests, we want to avoid 8 workers compiling the same files at the same time.
When assets did not change, we do not want to spend time compiling them.

Here is our solution for all that.

Its concept should work for all test suites.

Copy the following to config/initializers/webpacker_compile_once.rb. It will patch Webpacker, but only for the test environment:

# Avoid hardcoded asset host...

Cucumber: Test that an element is not overshadowed by another element

I needed to make sure that an element is visible and not overshadowed by an element that has a higher z-index (like a modal overlay).

Here is the step I wanted:

Then the very important notice should not be overshadowed by another element

This is the step definition:

Then(/^(.*?) should not be overshadowed by another element$/) do |locator|
  selector = selector_for(locator)
  expect(page).to have_css(selector)
  js = <<-JS
    var selector = #{selector.to_json};
    var elementFromSelector = document.querySelector(selector)...

Clicking issues with chromedriver

ChromeDriver clicking works by simulating a mouse click in the middle of the element's first client rect (or bounding client rect if it doesn't have a first client rect). (Clicking issues)

So if you are trying to click on an element, chromedriver tries to click at the position where it first finds that element.

This can lead to some problems and unexpected behavior like:

  • Element is not clickable at point ... erros
  • Another element which is now located at...

Middleman configuration for Rails Developers

Middleman is a static page generator that brings many of the goodies that Rails developers are used to.

Out of the box, Middleman brings Haml, Sass, helpers etc. However, it can be configured to do even better. This card is a list of improvement hints for a Rails developer.

Gemfile

Remove tzinfo-data and wdm unless you're on Windows. Add these gems:

gem 'middleman-livereload'
gem 'middleman-sprockets' # Asset pipeline!

gem 'bootstrap-sass' # If you want to use Bootstrap

gem 'byebug'

gem 'capistrano'
gem 'capistrano-mid...

ruby-sass: Do not use comments between selector definitions

Sass lets you easily specify multiple selectors at once like this:

.some-block
  &.has-hover,
  &:hover
    outline: 1px solid red

This will add a red outline on either real hover or when the has-hover class is present. However, adding a comment will void the definition of that line:

.some-block
  &.has-hover, // From hoverable.js <-- DON'T
  &:hover
    outline: 1px solid red

... will simply drop the &.has-hover part in ruby-sass(deprecated). [sassc](https://rubygems.org/g...

Bug in Chrome 56+ prevents filling in fields with slashes using selenium-webdriver/Capybara

There seems to be a nasty bug in Chrome 56 when testing with Selenium and Capybara: Slashes are not written to input fields with fill_in. A workaround is to use javascript / jquery to change the contents of an input field.

Use the following code or add the attached file to your features/support/-directory to overwrite fill_in.

module ChromedriverWorkarounds

  def fill_in(locator, options = {})
    text = options[:with].to_s
    if Capybara.current_driver == :selenium && text.include?('/')
      # There is a nasty Bug in Chrome ...

JavaScript: Don't throw synchronous exceptions from functions that return a Promise

TLDR: A function is hard to use when it sometimes returns a promise and sometimes throws an exception. When writing an async function, prefer to signal failure by returning a rejected promise.

The full story

When your function returns a promise ("async function"), try not to throw synchronous exceptions when encountering fatal errors.

So avoid this:

function foo(x) {
  if (!x) {
    throw "No x given"
  } else
    return new Promise(funct...

How to show jQuery event handler on element

Chrome gives you the currently selected element in the inspector with $0. If you select a button in the DOM you can set and inspect the event handler with the following two code lines:

$($0).on('click', function() { console.log('Hello') })
jQuery._data($0, "events").click[0].handler
// => "function () { console.log('Hello') }"

This is useful for debugging.

A collection of graph and diagram tools

Debugging cucumber feature with javascript + firefox vnc

TL;DR Debugging problems with javascript errors in cucumber tests is sometimes easier in the browser. Run the test, stop at the problematic point (with Then pause from Spreewald 1.7+) and open VNC for Firefox.

Features: