Show Navigation

Grails Promises

Learn how to use Grails Promises and load multiple REST payloads in parallel.

Authors: Sergio del Amo

Grails Version: 5.0.1

1 Grails Training

Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.

2 Getting Started

2.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

2.2 How to complete the guide

To get started do the following:

or

The Grails guides repositories contain two folders:

  • initial Initial project. Often a simple Grails app with some additional code to give you a head-start.

  • complete A completed example. It is the result of working through the steps presented by the guide and applying those changes to the initial folder.

To complete the guide, go to the initial folder

  • cd into grails-guides/grails-async-promises/initial

and follow the instructions in the next sections.

You can go right to the completed example if you cd into grails-guides/grails-async-promises/complete

2.3 Initial Project

The initial folder contains the sample Grails application developed during the Grails Guide: Consume and test a third-party REST API.

It contains code to invoke the Open Weather Map API to retrieve the weather forecast for a city.

The OpenWeathermapService.currentWeather does a network request and builds an Object with the received JSON Payload.

The goal is to display a grid of forecasts:

cities

3 Writing the Application

Added Grails 2.3 and since 3.3 an external project, the Async features of Grails aim to simplify concurrent programming within the framework and include the concept of Promises and a unified event model.

Your project already contains the async dependency:

build.gradle
    implementation "org.grails.plugins:async"

Grails Async capabilites documentation can be found in async.grails.org.

3.1 Top Cities

Add a class with the largest cities in USA.

src/main/groovy/demo/LargestUSCities.groovy
package demo

import groovy.transform.CompileStatic

@CompileStatic
class LargestUSCities {

    public static final List<String> CITIES = [
            'New York City',
            'Los Angeles',
            'Chicago',
            'Houston',
            'Philadelphia',
            'Phoenix',
            'San Antonio',
            'San Diego',
            'Dallas',
            'San Jose',
            'Austin',
            'Jacksonville',
            'Indianapolis',
            'San Francisco',
            'Columbus',
            'Fort Worth',
            'Charlotte',
            'Detroit',
            'El Paso',
            'Memphis',
            'Boston',
            'Seattle',
            'Denver',
            'Washington',
            'Nashville-Davidson',
            'Baltimore',
            'Louisville/Jefferson',
            'Portland',
            'Oklahoma',
            'Milwaukee',
            'Las Vegas',
            'Albuquerque',
            'Tucson',
            'Fresno',
            'Sacramento',
            'Long Beach',
            'Kansas',
            'Mesa',
            'Virginia Beach',
            'Atlanta',
            'Colorado Springs',
            'Raleigh',
            'Omaha',
            'Miami',
            'Oakland',
            'Tulsa',
            'Minneapolis',
            'Cleveland',
            'Wichita',
            'Arlington',
            'New Orleans',
            'Bakersfield',
            'Tampa',
            'Honolulu',
            'Anaheim',
            'Aurora',
            'Santa Ana',
            'St. Louis',
            'Riverside',
            'Corpus Christi',
            'Pittsburgh',
            'Lexington-Fayette',
            'Stockton',
            'Cincinnati',
            'St. Paul',
            'Toledo',
            'Newark',
            'Greensboro',
            'Plano',
            'Henderson',
            'Lincoln',
            'Buffalo',
            'Fort Wayne',
            'Jersey',
            'Chula Vista',
            'Orlando',
            'St. Petersburg',
            'Norfolk',
            'Chandler',
            'Laredo',
            'Madison',
            'Durham',
            'Lubbock',
            'Winston-Salem',
            'Garland',
            'Glendale',
            'Hialeah',
            'Reno',
            'Baton Rouge',
            'Irvine',
            'Chesapeake',
            'Irving',
            'Scottsdale',
            'North Las Vegas',
            'Fremont',
            'San Bernardino',
            'Boise',
            'Birmingham',
    ]
}

3.2 Open Weather Service

A Promise is a concept being embraced by many concurrency frameworks. They are similar to java.util.concurrent.Future instances, but include a more user friendly exception handling model, useful features like chaining and the ability to attach listeners.

In Grails the grails.async.Promises class provides the entry point to the Promise API:

grails-app/services/org/openweathermap/OpenweathermapService.groovy
import grails.async.Promise
import grails.async.PromiseList

Add to OpenweathermapService the following methods:

grails-app/services/org/openweathermap/OpenweathermapService.groovy
    @CompileDynamic
    Promise<List<CurrentWeather>> findCurrentWeatherByCitiesAndCountryCodeWithPromises(List<String> cities, String countryCode, Unit unit) {
        PromiseList<CurrentWeather> list = new PromiseList<CurrentWeather>()
        cities.each { String city ->
            list << task { (1)
                currentWeather(city, countryCode, unit)
            }
        }
        return list (2)
    }
1 The task method, which returns an instance of the grails.async.Promise.
2 A PromiseList is returned which contains a union of all of the created grails.async.Promise instances

Add an equivalent synchronous method.

grails-app/services/org/openweathermap/OpenweathermapService.groovy
    List<CurrentWeather> findCurrentWeatherByCitiesAndCountryCode(List<String> cities, String countryCode, Unit unit) {
        cities.collect { currentWeather(it, countryCode, unit) }
    }

3.3 Cities Controller

Create a controller named CitiesController which uses the previous service method:

grails-app/controllers/demo/CitiesController.groovy
package demo

import grails.async.Promise
import static grails.async.Promises.*
import groovy.transform.CompileStatic
import org.openweathermap.CurrentWeather
import org.openweathermap.OpenweathermapService
import org.openweathermap.Unit

@CompileStatic
class CitiesController {

    public static final String US = 'us'
    OpenweathermapService openweathermapService

    def index(String unit, boolean async) {
        Unit unitEnum = Unit.unitWithString(unit)

        if ( async ) { (1)
            Promise<List<CurrentWeather>> currentWeatherList = openweathermapService.findCurrentWeatherByCitiesAndCountryCodeWithPromises(LargestUSCities.CITIES, US, unitEnum)
            return tasks( (2)
                    currentWeatherList: currentWeatherList,
                    unit: createBoundPromise(unitEnum)
            )
        } else { (3)
            List<CurrentWeather> currentWeatherList = openweathermapService.findCurrentWeatherByCitiesAndCountryCode(LargestUSCities.CITIES, 'us', unitEnum)
            return [currentWeatherList: currentWeatherList, unit: unitEnum]
        }

    }
}
1 If the async parameter is true then use the previously created service method that returns a promise.
2 A set of named tasks is returned from the controller where each key becomes a resolved value in the model of the view. Grails will detect the fact that a promise is returned a create a non-blocking response. The createBoundPromise method is used to define a Promise that is already bound and doesn’t need to be resolved asynchronously.
3 If the async parameter is false then create the model synchronously.

3.4 View

Render the weather forecast grid with a GSP:

grails-app/views/cities/index.gsp
<html>
<head>
    <title>Largest US City Weather</title>
    <meta name="layout" content="main" />
</head>
<body>
<div id="content" role="main">
    <section class="row colset-2-its">
        <g:each in="${currentWeatherList}" var="${currentWeather}">
            <g:if test="${currentWeather}">
                <g:render template="/openweather/currentWeather"
                          model="[currentWeather: currentWeather, unit: unit]" />
            </g:if>
        </g:each>
    </section>
</div>
</body>
</html>

4 Running the Application

To run the application use the ./gradlew bootRun command which will start the application on port 8080.

Remember to setup a valid Open Weather Map API Key in application.yml

To fetch the weather forecast of the top largest USA cities leveraging the async capabilities of Grails visit:

runasync

Visit http://localhost:8080/cities?async=false to fetch weather forecasts synchronously.

runsync

5 Help with Grails

Object Computing, Inc. (OCI) sponsored the creation of this Guide. A variety of consulting and support services are available.

OCI is Home to Grails

Meet the Team