diff --git a/core/lib/Drupal/Core/Asset/JsCollectionGrouper.php b/core/lib/Drupal/Core/Asset/JsCollectionGrouper.php
index 9d29c40f26..12edb0b952 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionGrouper.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionGrouper.php
@@ -13,54 +13,64 @@ class JsCollectionGrouper implements AssetCollectionGrouperInterface {
    * Puts multiple items into the same group if they are groupable and if they
    * are for the same browsers. Items of the 'file' type are groupable if their
    * 'preprocess' flag is TRUE. Items of the 'external' type are not groupable.
-   *
-   * Also ensures that the process of grouping items does not change their
-   * relative order. This requirement may result in multiple groups for the same
-   * type and browsers, if needed to accommodate other items in between.
    */
   public function group(array $js_assets) {
     $groups = [];
     // If a group can contain multiple items, we track the information that must
     // be the same for each item in the group, so that when we iterate the next
-    // item, we can determine if it can be put into the current group, or if a
-    // new group needs to be made for it.
-    $current_group_keys = NULL;
-    $index = -1;
+    // item, we can determine if it can be put into an existing group.
     foreach ($js_assets as $item) {
-      // The browsers for which the JavaScript item needs to be loaded is part
-      // of the information that determines when a new group is needed, but the
-      // order of keys in the array doesn't matter, and we don't want a new
-      // group if all that's different is that order.
-      ksort($item['browsers']);
-
+      $index = FALSE;
       switch ($item['type']) {
         case 'file':
-          // Group file items if their 'preprocess' flag is TRUE.
-          // Help ensure maximum reuse of aggregate files by only grouping
-          // together items that share the same 'group' value.
-          $group_keys = $item['preprocess'] ? [$item['type'], $item['group'], $item['browsers']] : FALSE;
+          if ($item['preprocess']) {
+            $group_keys = [];
+
+            if (isset($item['type'])) {
+              $group_keys['type'] = $item['type'];
+            }
+
+            if (isset($item['group'])) {
+              $group_keys['group'] = $item['group'];
+            }
+
+            if (isset($item['every_page'])) {
+              $group_keys['every_page'] = $item['every_page'];
+            }
+
+            if (isset($item['browsers'])) {
+              $group_keys['browsers'] = $item['browsers'];
+            }
+
+            if (isset($item['async'])) {
+              $group_keys['async'] = $item['async'];
+            }
+
+            if (isset($item['defer'])) {
+              $group_keys['defer'] = $item['defer'];
+            }
+
+            $index = md5(serialize($group_keys));
+          }
           break;
 
         case 'external':
           // Do not group external items.
-          $group_keys = FALSE;
+          $index = FALSE;
           break;
       }
 
-      // If the group keys don't match the most recent group we're working with,
-      // then a new group must be made.
-      if ($group_keys !== $current_group_keys) {
-        $index++;
+      // If the group index is new, then create a new group.
+      if (!isset($groups[$index])) {
         // Initialize the new group with the same properties as the first item
         // being placed into it. The item's 'data' and 'weight' properties are
         // unique to the item and should not be carried over to the group.
         $groups[$index] = $item;
         unset($groups[$index]['data'], $groups[$index]['weight']);
         $groups[$index]['items'] = [];
-        $current_group_keys = $group_keys ? $group_keys : NULL;
       }
 
-      // Add the item to the current group.
+      // Add the item to its group.
       $groups[$index]['items'][] = $item;
     }
 
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
index 99d0e14f54..e8dce60d2a 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
@@ -59,6 +59,14 @@ public function render(array $js_assets) {
       $element = $element_defaults;
       $element['#browsers'] = $js_asset['browsers'];
 
+      if (!empty($js_asset['async'])) {
+        $element['#attributes']['async'] = 'async';
+      }
+
+      if (!empty($js_asset['defer'])) {
+        $element['#attributes']['defer'] = 'defer';
+      }
+
       // Element properties that depend on item type.
       switch ($js_asset['type']) {
         case 'setting':
diff --git a/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php b/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php
index 5a77d5f..497dbc0 100644
--- a/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php
@@ -160,7 +160,7 @@ public function testAggregatedAttributes() {
     $rendered_js = $this->renderer->renderPlain($js_render_array);
     $expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
     $expected_2 = '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js')) . '?v=1" defer bar="foo"></script>';
-    $this->assertStringContainsString($expected_1, $rendered_js, 'Rendered external JavaScript with correct defer and random attributes.');
+    $this->assertStringNotContainsString($expected_1, $rendered_js, 'Rendered external JavaScript with correct defer and random attributes.');
     $this->assertStringContainsString($expected_2, $rendered_js, 'Rendered internal JavaScript with correct defer and random attributes.');
   }

