Bug 1466691 - Replace callback style in favor of promise for TabClient methods. r=jryans draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Tue, 05 Jun 2018 01:50:40 -0700
changeset 806962 1b9f61c46290c56f0292c841697b4cb7562ef372
parent 806961 99d1d0235c4697425cd92061e6043babfc7fa345
push id113003
push userbmo:poirot.alex@gmail.com
push dateTue, 12 Jun 2018 23:23:38 +0000
reviewersjryans
bugs1466691
milestone62.0a1
Bug 1466691 - Replace callback style in favor of promise for TabClient methods. r=jryans MozReview-Commit-ID: 6Is4O8KQhgY
devtools/client/debugger/debugger-controller.js
devtools/client/debugger/new/panel.js
devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js
devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
devtools/client/debugger/test/mochitest/head.js
devtools/client/framework/attach-thread.js
devtools/client/framework/devtools-browser.js
devtools/client/framework/target.js
devtools/client/scratchpad/scratchpad.js
devtools/client/webconsole/webconsole-connection-proxy.js
devtools/docs/backend/client-api.md
devtools/server/tests/mochitest/inspector-helpers.js
devtools/server/tests/unit/head_dbg.js
devtools/server/tests/unit/test_attach.js
devtools/server/tests/unit/test_dbgclient_debuggerstatement.js
devtools/server/tests/unit/test_interrupt.js
devtools/server/tests/unit/test_reattach-thread.js
devtools/server/tests/unit/test_xpcshell_debugging.js
devtools/shared/client/debugger-client.js
devtools/shared/client/tab-client.js
devtools/shared/client/worker-client.js
devtools/shared/gcli/source/lib/gcli/util/host.js
devtools/shared/webconsole/test/common.js
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -518,17 +518,17 @@ Workers.prototype = {
     });
   },
 
   _onWorkerListChanged: function () {
     this._updateWorkerList();
   },
 
   _onWorkerSelect: function (workerForm) {
-    DebuggerController.client.attachWorker(workerForm.actor, (response, workerClient) => {
+    DebuggerController.client.attachWorker(workerForm.actor).then(([response, workerClient]) => {
       let toolbox = gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
                                           "jsdebugger", Toolbox.HostType.WINDOW);
       window.emit(EVENTS.WORKER_SELECTED, toolbox);
     });
   }
 };
 
 /**
--- a/devtools/client/debugger/new/panel.js
+++ b/devtools/client/debugger/new/panel.js
@@ -78,28 +78,22 @@ DebuggerPanel.prototype = {
       return;
     }
 
     top.openWebLinkIn(url, "tab", {
       triggeringPrincipal: win.document.nodePrincipal
     });
   },
 
-  openWorkerToolbox: function(worker) {
-    this.toolbox.target.client.attachWorker(
-      worker.actor,
-      (response, workerClient) => {
-        const workerTarget = TargetFactory.forWorker(workerClient);
-        gDevTools
-          .showToolbox(workerTarget, "jsdebugger", Toolbox.HostType.WINDOW)
-          .then(toolbox => {
-            toolbox.once("destroy", () => workerClient.detach());
-          });
-      }
-    );
+  openWorkerToolbox: async function(worker) {
+    const [response, workerClient] =
+      await this.toolbox.target.client.attachWorker(worker.actor);
+    const workerTarget = TargetFactory.forWorker(workerClient);
+    const toolbox = await gDevTools.showToolbox(workerTarget, "jsdebugger", Toolbox.HostType.WINDOW);
+    toolbox.once("destroy", () => workerClient.detach());
   },
 
   getFrames: function() {
     let frames = this._selectors.getFrames(this._getState());
 
     // Frames is null when the debugger is not paused.
     if (!frames) {
       return {
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js
@@ -24,24 +24,20 @@ function initDebuggerClient() {
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
   DebuggerServer.allowChromeProcess = true;
 
   let transport = DebuggerServer.connectPipe();
   return new DebuggerClient(transport);
 }
 
-function attachThread(client, actor) {
-  return new Promise(resolve => {
-    client.attachTab(actor, (response, tabClient) => {
-      tabClient.attachThread(null, (r, threadClient) => {
-        resolve(threadClient);
-      });
-    });
-  });
+async function attachThread(client, actor) {
+  let [response, tabClient] = await client.attachTab(actor);
+  let [response2, threadClient] = await tabClient.attachThread(null);
+  return threadClient;
 }
 
 function onNewGlobal() {
   ok(true, "Received a new chrome global.");
   gClient.removeListener("newGlobal", onNewGlobal);
   gNewGlobal.resolve();
 }
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
@@ -41,18 +41,18 @@ function test() {
   });
 }
 
 function testParentProcessTargetActor() {
   gClient.getProcess().then(aResponse => {
     gClient.addListener("newGlobal", onNewGlobal);
 
     let actor = aResponse.form.actor;
-    gClient.attachTab(actor, (response, tabClient) => {
-      tabClient.attachThread(null, (aResponse, aThreadClient) => {
+    gClient.attachTab(actor).then(([response, tabClient]) => {
+      tabClient.attachThread(null).then(([aResponse, aThreadClient]) => {
         gThreadClient = aThreadClient;
         gThreadClient.addListener("newSource", onNewSource);
 
         if (aResponse.error) {
           ok(false, "Couldn't attach to the chrome debugger.");
           gAttached.reject();
         } else {
           ok(true, "Attached to the chrome debugger.");
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -178,40 +178,27 @@ function getAddonActorForId(aClient, aAd
     let addonTargetActor = aResponse.addons.filter(aGrip => aGrip.id == aAddonId).pop();
     info("got addon actor for ID: " + aAddonId);
     deferred.resolve(addonTargetActor);
   });
 
   return deferred.promise;
 }
 
-function attachTabActorForUrl(aClient, aUrl) {
-  let deferred = promise.defer();
-
-  getTabActorForUrl(aClient, aUrl).then(aGrip => {
-    aClient.attachTab(aGrip.actor, aResponse => {
-      deferred.resolve([aGrip, aResponse]);
-    });
-  });
-
-  return deferred.promise;
+async function attachTabActorForUrl(aClient, aUrl) {
+  let grip = await getTabActorForUrl(aClient, aUrl);
+  let [ response ] = await aClient.attachTab(grip.actor);
+  return [grip, response];
 }
 
-function attachThreadActorForUrl(aClient, aUrl) {
-  let deferred = promise.defer();
-
-  attachTabActorForUrl(aClient, aUrl).then(([aGrip, aResponse]) => {
-    aClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => {
-      aThreadClient.resume(aResponse => {
-        deferred.resolve(aThreadClient);
-      });
-    });
-  });
-
-  return deferred.promise;
+async function attachThreadActorForUrl(aClient, aUrl) {
+  let [grip, response] = await attachTabActorForUrl(aClient, aUrl);
+  let [response2, threadClient] = await aClient.attachThread(response.threadActor);
+  await threadClient.resume();
+  return threadClient;
 }
 
 // Override once from shared-head, as some tests depend on trying native DOM listeners
 // before EventEmitter.  Since this directory is deprecated, there's little value in
 // resolving the descrepency here.
 this.once = function (aTarget, aEventName, aUseCapture = false) {
   info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
 
@@ -674,26 +661,24 @@ AddonDebugger.prototype = {
     yield this.client.close();
     yield this.debuggerPanel._toolbox.destroy();
     this.frame.remove();
     window.removeEventListener("message", this._onMessage);
   }),
 
   _attachConsole: function () {
     let deferred = promise.defer();
-    this.client.attachConsole(this.target.form.consoleActor, ["ConsoleAPI"], (aResponse, aWebConsoleClient) => {
-      if (aResponse.error) {
-        deferred.reject(aResponse);
-      }
-      else {
+    this.client.attachConsole(this.target.form.consoleActor, ["ConsoleAPI"])
+      .then(([aResponse, aWebConsoleClient]) => {
         this.webConsole = aWebConsoleClient;
         this.client.addListener("consoleAPICall", this._onConsoleAPICall);
         deferred.resolve();
-      }
-    });
+      }, e => {
+        deferred.reject(e); 
+      });
     return deferred.promise;
   },
 
   _onConsoleAPICall: function (aType, aPacket) {
     if (aPacket.from != this.webConsole.actor)
       return;
     this.emit("console", aPacket.message);
   },
@@ -951,17 +936,17 @@ function hideVarPopupByScrollingEditor(a
 function reopenVarPopup(...aArgs) {
   return hideVarPopup.apply(this, aArgs).then(() => openVarPopup.apply(this, aArgs));
 }
 
 function attachAddonActorForId(aClient, aAddonId) {
   let deferred = promise.defer();
 
   getAddonActorForId(aClient, aAddonId).then(aGrip => {
-    aClient.attachAddon(aGrip.actor, aResponse => {
+    aClient.attachAddon(aGrip.actor).then(([aResponse]) => {
       deferred.resolve([aGrip, aResponse]);
     });
   });
 
   return deferred.promise;
 }
 
 function doResume(aPanel) {
@@ -1103,21 +1088,17 @@ function findTab(tabs, url) {
       return tab;
     }
   }
   return null;
 }
 
 function attachTab(client, tab) {
   info("Attaching to tab with url '" + tab.url + "'.");
-  return new Promise(function (resolve) {
-    client.attachTab(tab.actor, function (response, tabClient) {
-      resolve([response, tabClient]);
-    });
-  });
+  return client.attachTab(tab.actor);
 }
 
 function listWorkers(tabClient) {
   info("Listing workers.");
   return new Promise(function (resolve) {
     tabClient.listWorkers(function (response) {
       resolve(response);
     });
@@ -1131,40 +1112,32 @@ function findWorker(workers, url) {
       return worker;
     }
   }
   return null;
 }
 
 function attachWorker(tabClient, worker) {
   info("Attaching to worker with url '" + worker.url + "'.");
-  return new Promise(function (resolve, reject) {
-    tabClient.attachWorker(worker.actor, function (response, workerClient) {
-      resolve([response, workerClient]);
-    });
-  });
+  return tabClient.attachWorker(worker.actor);
 }
 
 function waitForWorkerListChanged(tabClient) {
   info("Waiting for worker list to change.");
   return new Promise(function (resolve) {
     tabClient.addListener("workerListChanged", function listener() {
       tabClient.removeListener("workerListChanged", listener);
       resolve();
     });
   });
 }
 
 function attachThread(workerClient, options) {
   info("Attaching to thread.");
-  return new Promise(function (resolve, reject) {
-    workerClient.attachThread(options, function (response, threadClient) {
-      resolve([response, threadClient]);
-    });
-  });
+  return workerClient.attachThread(options);
 }
 
 function waitForWorkerClose(workerClient) {
   info("Waiting for worker to close.");
   return new Promise(function (resolve) {
     workerClient.addOneTimeListener("close", function () {
       info("Worker did close.");
       resolve();
--- a/devtools/client/framework/attach-thread.js
+++ b/devtools/client/framework/attach-thread.js
@@ -53,17 +53,17 @@ function attachThread(toolbox) {
     useSourceMaps = Services.prefs.getBoolPref("devtools.debugger.source-maps-enabled");
     autoBlackBox = Services.prefs.getBoolPref("devtools.debugger.auto-black-box");
   } else {
     ignoreFrameEnvironment = true;
   }
 
   const threadOptions = { useSourceMaps, autoBlackBox, ignoreFrameEnvironment };
 
-  const handleResponse = (res, threadClient) => {
+  const handleResponse = ([res, threadClient]) => {
     if (res.error) {
       deferred.reject(new Error("Couldn't attach to thread: " + res.error));
       return;
     }
     threadClient.addListener("paused", handleThreadState.bind(null, toolbox));
     threadClient.addListener("resumed", handleThreadState.bind(null, toolbox));
 
     if (!threadClient.paused) {
@@ -93,25 +93,25 @@ function attachThread(toolbox) {
       }
 
       deferred.resolve(threadClient);
     });
   };
 
   if (target.isBrowsingContext) {
     // Attaching a tab, a browser process, or a WebExtensions add-on.
-    target.activeTab.attachThread(threadOptions, handleResponse);
+    target.activeTab.attachThread(threadOptions).then(handleResponse);
   } else if (target.isAddon) {
     // Attaching a legacy addon.
-    target.client.attachAddon(actor, res => {
-      target.client.attachThread(res.threadActor, handleResponse);
+    target.client.attachAddon(actor).then(([res]) => {
+      target.client.attachThread(res.threadActor).then(handleResponse);
     });
   } else {
     // Attaching an old browser debugger or a content process.
-    target.client.attachThread(chromeDebugger, handleResponse);
+    target.client.attachThread(chromeDebugger).then(handleResponse);
   }
 
   return deferred.promise;
 }
 
 function detachThread(threadClient) {
   threadClient.removeListener("paused");
   threadClient.removeListener("resumed");
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -370,24 +370,21 @@ var gDevToolsBrowser = exports.gDevTools
   /**
    * Open a window-hosted toolbox to debug the worker associated to the provided
    * worker actor.
    *
    * @param  {DebuggerClient} client
    * @param  {Object} workerTargetActor
    *         worker actor form to debug
    */
-  openWorkerToolbox(client, workerTargetActor) {
-    client.attachWorker(workerTargetActor, (response, workerClient) => {
-      const workerTarget = TargetFactory.forWorker(workerClient);
-      gDevTools.showToolbox(workerTarget, null, Toolbox.HostType.WINDOW)
-        .then(toolbox => {
-          toolbox.once("destroy", () => workerClient.detach());
-        });
-    });
+  async openWorkerToolbox(client, workerTargetActor) {
+    const [, workerClient] = await client.attachWorker(workerTargetActor);
+    const workerTarget = TargetFactory.forWorker(workerClient);
+    const toolbox = await gDevTools.showToolbox(workerTarget, null, Toolbox.HostType.WINDOW);
+    toolbox.once("destroy", () => workerClient.detach());
   },
 
   /**
    * Install WebIDE widget
    */
   // Used by itself
   installWebIDEWidget() {
     if (this.isWebIDEWidgetInstalled()) {
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -434,44 +434,43 @@ TabTarget.prototype = {
 
       this._form = form;
       this._url = form.url;
       this._title = form.title;
     }
 
     this._setupRemoteListeners();
 
-    const attachTab = () => {
-      this._client.attachTab(this._form.actor, (response, tabClient) => {
-        if (!tabClient) {
-          this._remote.reject("Unable to attach to the tab");
-          return;
-        }
+    const attachTab = async () => {
+      try {
+        const [ response, tabClient ] = await this._client.attachTab(this._form.actor);
         this.activeTab = tabClient;
         this.threadActor = response.threadActor;
-
-        attachConsole();
-      });
+      } catch (e) {
+        this._remote.reject("Unable to attach to the tab: " + e);
+        return;
+      }
+      attachConsole();
     };
 
-    const onConsoleAttached = (response, consoleClient) => {
-      if (!consoleClient) {
-        this._remote.reject("Unable to attach to the console");
-        return;
-      }
+    const onConsoleAttached = ([response, consoleClient]) => {
       this.activeConsole = consoleClient;
 
       this._onInspectObject = packet => this.emit("inspect-object", packet);
       this.activeConsole.on("inspectObject", this._onInspectObject);
 
       this._remote.resolve(null);
     };
 
     const attachConsole = () => {
-      this._client.attachConsole(this._form.consoleActor, [], onConsoleAttached);
+      this._client.attachConsole(this._form.consoleActor, [])
+        .then(onConsoleAttached, response => {
+          this._remote.reject(
+            `Unable to attach to the console [${response.error}]: ${response.message}`);
+        });
     };
 
     if (this.isLocalTab) {
       this._client.connect()
         .then(() => this._client.getTab({ tab: this.tab }))
         .then(response => {
           this._form = response.tab;
           this._url = this._form.url;
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -2051,27 +2051,26 @@ ScratchpadTab.prototype = {
       });
     }, REMOTE_TIMEOUT);
 
     deferred.promise.then(() => clearTimeout(connectTimer));
 
     this._attach(aSubject).then(aTarget => {
       const consoleActor = aTarget.form.consoleActor;
       const client = aTarget.client;
-      client.attachConsole(consoleActor, [], (aResponse, aWebConsoleClient) => {
-        if (aResponse.error) {
-          reportError("attachConsole", aResponse);
-          deferred.reject(aResponse);
-        } else {
+      client.attachConsole(consoleActor, [])
+        .then(([aResponse, aWebConsoleClient]) => {
           deferred.resolve({
             webConsoleClient: aWebConsoleClient,
             debuggerClient: client
           });
-        }
-      });
+        }, error => {
+          reportError("attachConsole", error);
+          deferred.reject(error);
+        });
     });
 
     return deferred.promise;
   },
 
   /**
    * Attach to this tab.
    *
--- a/devtools/client/webconsole/webconsole-connection-proxy.js
+++ b/devtools/client/webconsole/webconsole-connection-proxy.js
@@ -174,38 +174,37 @@ WebConsoleConnectionProxy.prototype = {
   _attachConsole: function() {
     const listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
                        "FileActivity"];
     // Enable the forwarding of console messages to the parent process
     // when we open the Browser Console or Toolbox.
     if (this.target.chrome && !this.target.isAddon) {
       listeners.push("ContentProcessMessages");
     }
-    this.client.attachConsole(this._consoleActor, listeners,
-                              this._onAttachConsole);
+    this.client.attachConsole(this._consoleActor, listeners)
+      .then(this._onAttachConsole, response => {
+        if (response.error) {
+          console.error("attachConsole failed: " + response.error + " " +
+                        response.message);
+          this._connectDefer.reject(response);
+        }
+      });
   },
 
   /**
    * The "attachConsole" response handler.
    *
    * @private
    * @param object response
    *        The JSON response object received from the server.
    * @param object webConsoleClient
    *        The WebConsoleClient instance for the attached console, for the
    *        specific tab we work with.
    */
-  _onAttachConsole: function(response, webConsoleClient) {
-    if (response.error) {
-      console.error("attachConsole failed: " + response.error + " " +
-                    response.message);
-      this._connectDefer.reject(response);
-      return;
-    }
-
+  _onAttachConsole: function([response, webConsoleClient]) {
     this.webConsoleClient = webConsoleClient;
     this._hasNativeConsoleAPI = response.nativeConsoleAPI;
 
     let saveBodies = Services.prefs.getBoolPref(
       "devtools.netmonitor.saveRequestAndResponseBodies");
 
     // There is no way to view response bodies from the Browser Console, so do
     // not waste the memory.
--- a/devtools/docs/backend/client-api.md
+++ b/devtools/docs/backend/client-api.md
@@ -80,17 +80,17 @@ Attaching to a browser tab requires enum
 ```javascript
 function attachToTab() {
   // Get the list of tabs to find the one to attach to.
   client.listTabs().then((response) => {
     // Find the active tab.
     let tab = response.tabs[response.selected];
 
     // Attach to the tab.
-    client.attachTab(tab.actor, (response, tabClient) => {
+    client.attachTab(tab.actor).then(([response, tabClient]) => {
       if (!tabClient) {
         return;
       }
 
       // Now the tabClient is ready and can be used.
     });
   });
 }
@@ -118,17 +118,17 @@ function onTab() {
 ## Debugging JavaScript running in a browser tab
 
 Once the application is attached to a tab, it can attach to its thread in order to interact with the JavaScript debugger:
 
 ```javascript
 // Assuming the application is already attached to the tab, and response is the first
 // argument of the attachTab callback.
 
-client.attachThread(response.threadActor, function(response, threadClient) {
+client.attachThread(response.threadActor).then(function([response, threadClient]) {
   if (!threadClient) {
     return;
   }
 
   // Attach listeners for thread events.
   threadClient.addListener("paused", onPause);
   threadClient.addListener("resumed", fooListener);
   threadClient.addListener("detached", fooListener);
@@ -190,17 +190,17 @@ function shutdownDebugger() {
  * Start debugging the current tab.
  */
 function debugTab() {
   // Get the list of tabs to find the one to attach to.
   client.listTabs().then(response => {
     // Find the active tab.
     let tab = response.tabs[response.selected];
     // Attach to the tab.
-    client.attachTab(tab.actor, (response, tabClient) => {
+    client.attachTab(tab.actor).then(([response, tabClient]) => {
       if (!tabClient) {
         return;
       }
 
       // Attach to the thread (context).
       client.attachThread(response.threadActor, (response, thread) => {
         if (!thread) {
           return;
--- a/devtools/server/tests/mochitest/inspector-helpers.js
+++ b/devtools/server/tests/mochitest/inspector-helpers.js
@@ -62,17 +62,17 @@ function attachURL(url, callback) {
     if (event.data === "ready") {
       client = new DebuggerClient(DebuggerServer.connectPipe());
       client.connect().then(([applicationType, traits]) => {
         client.listTabs().then(response => {
           for (const tab of response.tabs) {
             if (tab.url === url) {
               window.removeEventListener("message", loadListener);
               // eslint-disable-next-line max-nested-callbacks
-              client.attachTab(tab.actor, function(_response, _tabClient) {
+              client.attachTab(tab.actor).then(function() {
                 try {
                   callback(null, client, tab, win.document);
                 } catch (ex) {
                   Cu.reportError(ex);
                   dump(ex);
                 }
               });
               break;
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -352,33 +352,35 @@ function getTestTab(client, title, callb
     callback(null);
   });
 }
 
 // Attach to |client|'s tab whose title is |title|; pass |callback| the
 // response packet and a TabClient instance referring to that tab.
 function attachTestTab(client, title, callback) {
   getTestTab(client, title, function(tab) {
-    client.attachTab(tab.actor, callback);
+    client.attachTab(tab.actor).then(([response, tabClient]) => {
+      callback(response, tabClient);
+    });
   });
 }
 
 // Attach to |client|'s tab whose title is |title|, and then attach to
 // that tab's thread. Pass |callback| the thread attach response packet, a
 // TabClient referring to the tab, and a ThreadClient referring to the
 // thread.
 function attachTestThread(client, title, callback) {
   attachTestTab(client, title, function(tabResponse, tabClient) {
-    function onAttach(response, threadClient) {
+    function onAttach([response, threadClient]) {
       callback(response, tabClient, threadClient, tabResponse);
     }
     tabClient.attachThread({
       useSourceMaps: true,
       autoBlackBox: true
-    }, onAttach);
+    }).then(onAttach);
   });
 }
 
 // Attach to |client|'s tab whose title is |title|, attach to the tab's
 // thread, and then resume it. Pass |callback| the thread's response to
 // the 'resume' packet, a TabClient for the tab, and a ThreadClient for the
 // thread.
 function attachTestTabAndResume(client, title, callback = () => {}) {
--- a/devtools/server/tests/unit/test_attach.js
+++ b/devtools/server/tests/unit/test_attach.js
@@ -17,17 +17,17 @@ function run_test() {
     attachTestTab(gClient, "test-1", function(reply, tabClient) {
       test_attach(tabClient);
     });
   });
   do_test_pending();
 }
 
 function test_attach(tabClient) {
-  tabClient.attachThread({}, function(response, threadClient) {
+  tabClient.attachThread({}).then(function([response, threadClient]) {
     Assert.equal(threadClient.state, "paused");
     threadClient.resume(cleanup);
   });
 }
 
 function cleanup() {
   gClient.addListener("closed", function(event) {
     do_test_finished();
--- a/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js
+++ b/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js
@@ -20,17 +20,17 @@ function run_test() {
       test_threadAttach(reply.threadActor);
     });
   });
   do_test_pending();
 }
 
 function test_threadAttach(threadActorID) {
   info("Trying to attach to thread " + threadActorID);
-  gTabClient.attachThread({}, function(response, threadClient) {
+  gTabClient.attachThread({}).then(function([response, threadClient]) {
     Assert.equal(threadClient.state, "paused");
     Assert.equal(threadClient.actor, threadActorID);
     threadClient.resume(function() {
       Assert.equal(threadClient.state, "attached");
       test_debugger_statement(threadClient);
     });
   });
 }
--- a/devtools/server/tests/unit/test_interrupt.js
+++ b/devtools/server/tests/unit/test_interrupt.js
@@ -16,17 +16,17 @@ function run_test() {
   gClient = new DebuggerClient(transport);
   gClient.connect().then(function(type, traits) {
     attachTestTab(gClient, "test-1", test_attach);
   });
   do_test_pending();
 }
 
 function test_attach(response, tabClient) {
-  tabClient.attachThread({}, function(response, threadClient) {
+  tabClient.attachThread({}).then(function([response, threadClient]) {
     Assert.equal(threadClient.paused, true);
     threadClient.resume(function() {
       test_interrupt(threadClient);
     });
   });
 }
 
 function test_interrupt(threadClient) {
--- a/devtools/server/tests/unit/test_reattach-thread.js
+++ b/devtools/server/tests/unit/test_reattach-thread.js
@@ -21,33 +21,33 @@ function run_test() {
       gTabClient = tabClient;
       test_attach();
     });
   });
   do_test_pending();
 }
 
 function test_attach() {
-  gTabClient.attachThread({}, (response, threadClient) => {
+  gTabClient.attachThread({}).then(([response, threadClient]) => {
     Assert.equal(threadClient.state, "paused");
     gThreadClient = threadClient;
     threadClient.resume(test_detach);
   });
 }
 
 function test_detach() {
   gThreadClient.detach(() => {
     Assert.equal(gThreadClient.state, "detached");
     Assert.equal(gTabClient.thread, null);
     test_reattach();
   });
 }
 
 function test_reattach() {
-  gTabClient.attachThread({}, (response, threadClient) => {
+  gTabClient.attachThread({}).then(([response, threadClient]) => {
     Assert.notEqual(gThreadClient, threadClient);
     Assert.equal(threadClient.state, "paused");
     Assert.equal(gTabClient.thread, threadClient);
     threadClient.resume(cleanup);
   });
 }
 
 function cleanup() {
--- a/devtools/server/tests/unit/test_xpcshell_debugging.js
+++ b/devtools/server/tests/unit/test_xpcshell_debugging.js
@@ -17,18 +17,18 @@ function run_test() {
     testResumed = true;
   });
   const transport = DebuggerServer.connectPipe();
   const client = new DebuggerClient(transport);
   client.connect().then(() => {
     // Even though we have no tabs, listTabs gives us the chromeDebugger.
     client.getProcess().then(response => {
       const actor = response.form.actor;
-      client.attachTab(actor, (response, tabClient) => {
-        tabClient.attachThread(null, (response, threadClient) => {
+      client.attachTab(actor).then(([response, tabClient]) => {
+        tabClient.attachThread(null).then(([response, threadClient]) => {
           threadClient.addOneTimeListener("paused", (event, packet) => {
             equal(packet.why.type, "breakpoint",
                 "yay - hit the breakpoint at the first line in our script");
             // Resume again - next stop should be our "debugger" statement.
             threadClient.addOneTimeListener("paused", (event, packet) => {
               equal(packet.why.type, "debuggerStatement",
                     "yay - hit the 'debugger' statement in our script");
               threadClient.resume(() => {
--- a/devtools/shared/client/debugger-client.js
+++ b/devtools/shared/client/debugger-client.js
@@ -24,18 +24,16 @@ loader.lazyRequireGetter(this, "getDevic
 loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
 loader.lazyRequireGetter(this, "AddonClient", "devtools/shared/client/addon-client");
 loader.lazyRequireGetter(this, "RootClient", "devtools/shared/client/root-client");
 loader.lazyRequireGetter(this, "TabClient", "devtools/shared/client/tab-client");
 loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
 loader.lazyRequireGetter(this, "WorkerClient", "devtools/shared/client/worker-client");
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/object-client");
 
-const noop = () => {};
-
 // Define the minimum officially supported version of Firefox when connecting to a remote
 // runtime. (Use ".0a1" to support the very first nightly version)
 // This is usually the current ESR version.
 const MIN_SUPPORTED_PLATFORM_VERSION = "52.0a1";
 const MS_PER_DAY = 86400000;
 
 /**
  * Creates a client for the remote debugging protocol server. This client
@@ -325,163 +323,126 @@ DebuggerClient.prototype = {
     return this.mainRoot.getTab(filter);
   },
 
   /**
    * Attach to a tab's target actor.
    *
    * @param string targetActor
    *        The target actor ID for the tab to attach.
-   * @param function onResponse
-   *        Called with the response packet and a TabClient
-   *        (which will be undefined on error).
    */
-  attachTab: function(targetActor, onResponse = noop) {
+  attachTab: function(targetActor) {
     if (this._clients.has(targetActor)) {
       const cachedTab = this._clients.get(targetActor);
       const cachedResponse = {
         cacheDisabled: cachedTab.cacheDisabled,
         javascriptEnabled: cachedTab.javascriptEnabled,
         traits: cachedTab.traits,
       };
-      DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
       return promise.resolve([cachedResponse, cachedTab]);
     }
 
     const packet = {
       to: targetActor,
       type: "attach"
     };
     return this.request(packet).then(response => {
-      let tabClient;
-      if (!response.error) {
-        tabClient = new TabClient(this, response);
-        this.registerClient(tabClient);
-      }
-      onResponse(response, tabClient);
+      const tabClient = new TabClient(this, response);
+      this.registerClient(tabClient);
       return [response, tabClient];
     });
   },
 
-  attachWorker: function(workerTargetActor, onResponse = noop) {
+  attachWorker: function(workerTargetActor) {
     let workerClient = this._clients.get(workerTargetActor);
     if (workerClient !== undefined) {
       const response = {
         from: workerClient.actor,
         type: "attached",
         url: workerClient.url
       };
-      DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
       return promise.resolve([response, workerClient]);
     }
 
     return this.request({ to: workerTargetActor, type: "attach" }).then(response => {
-      if (response.error) {
-        onResponse(response, null);
-        return [response, null];
-      }
-
       workerClient = new WorkerClient(this, response);
       this.registerClient(workerClient);
-      onResponse(response, workerClient);
       return [response, workerClient];
     });
   },
 
   /**
    * Attach to an addon target actor.
    *
    * @param string addonTargetActor
    *        The actor ID for the addon to attach.
-   * @param function onResponse
-   *        Called with the response packet and a AddonClient
-   *        (which will be undefined on error).
    */
-  attachAddon: function(addonTargetActor, onResponse = noop) {
+  attachAddon: function(addonTargetActor) {
     const packet = {
       to: addonTargetActor,
       type: "attach"
     };
     return this.request(packet).then(response => {
-      let addonClient;
-      if (!response.error) {
-        addonClient = new AddonClient(this, addonTargetActor);
-        this.registerClient(addonClient);
-        this.activeAddon = addonClient;
-      }
-      onResponse(response, addonClient);
+      const addonClient = new AddonClient(this, addonTargetActor);
+      this.registerClient(addonClient);
+      this.activeAddon = addonClient;
       return [response, addonClient];
     });
   },
 
   /**
    * Attach to a Web Console actor.
    *
    * @param string consoleActor
    *        The ID for the console actor to attach to.
    * @param array listeners
    *        The console listeners you want to start.
-   * @param function onResponse
-   *        Called with the response packet and a WebConsoleClient
-   *        instance (which will be undefined on error).
    */
-  attachConsole:
-  function(consoleActor, listeners, onResponse = noop) {
+  attachConsole: function(consoleActor, listeners) {
     const packet = {
       to: consoleActor,
       type: "startListeners",
       listeners: listeners,
     };
 
     return this.request(packet).then(response => {
       let consoleClient;
-      if (!response.error) {
-        if (this._clients.has(consoleActor)) {
-          consoleClient = this._clients.get(consoleActor);
-        } else {
-          consoleClient = new WebConsoleClient(this, response);
-          this.registerClient(consoleClient);
-        }
+      if (this._clients.has(consoleActor)) {
+        consoleClient = this._clients.get(consoleActor);
+      } else {
+        consoleClient = new WebConsoleClient(this, response);
+        this.registerClient(consoleClient);
       }
-      onResponse(response, consoleClient);
       return [response, consoleClient];
     });
   },
 
   /**
    * Attach to a global-scoped thread actor for chrome debugging.
    *
    * @param string threadActor
    *        The actor ID for the thread to attach.
-   * @param function onResponse
-   *        Called with the response packet and a ThreadClient
-   *        (which will be undefined on error).
    * @param object options
    *        Configuration options.
    *        - useSourceMaps: whether to use source maps or not.
    */
-  attachThread: function(threadActor, onResponse = noop, options = {}) {
+  attachThread: function(threadActor, options = {}) {
     if (this._clients.has(threadActor)) {
       const client = this._clients.get(threadActor);
-      DevToolsUtils.executeSoon(() => onResponse({}, client));
       return promise.resolve([{}, client]);
     }
 
     const packet = {
       to: threadActor,
       type: "attach",
       options,
     };
     return this.request(packet).then(response => {
-      let threadClient;
-      if (!response.error) {
-        threadClient = new ThreadClient(this, threadActor);
-        this.registerClient(threadClient);
-      }
-      onResponse(response, threadClient);
+      const threadClient = new ThreadClient(this, threadActor);
+      this.registerClient(threadClient);
       return [response, threadClient];
     });
   },
 
   /**
    * Fetch the ChromeActor for the main process or ChildProcessActor for a
    * a given child process ID.
    *
@@ -500,19 +461,16 @@ DebuggerClient.prototype = {
     return this.request(packet);
   },
 
   /**
    * Release an object actor.
    *
    * @param string actor
    *        The actor ID to send the request to.
-   * @param onResponse function
-   *        If specified, will be called with the response packet when
-   *        debugging server responds.
    */
   release: DebuggerClient.requester({
     to: arg(0),
     type: "release"
   }),
 
   /**
    * Send a request to the debugging server.
--- a/devtools/shared/client/tab-client.js
+++ b/devtools/shared/client/tab-client.js
@@ -1,23 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const promise = require("devtools/shared/deprecated-sync-thenables");
 
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const eventSource = require("devtools/shared/client/event-source");
 const {arg, DebuggerClient} = require("devtools/shared/client/debugger-client");
 loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
 
-const noop = () => {};
-
 /**
  * Creates a tab client for the remote debugging protocol server. This client
  * is a front to the tab actor created in the server side, hiding the protocol
  * details in a traditional JavaScript API.
  *
  * @param client DebuggerClient
  *        The debugger client parent.
  * @param form object
@@ -44,46 +41,36 @@ TabClient.prototype = {
   },
 
   /**
    * Attach to a thread actor.
    *
    * @param object options
    *        Configuration options.
    *        - useSourceMaps: whether to use source maps or not.
-   * @param function onResponse
-   *        Called with the response packet and a ThreadClient
-   *        (which will be undefined on error).
    */
-  attachThread: function(options = {}, onResponse = noop) {
+  attachThread: function(options = {}) {
     if (this.thread) {
-      DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
       return promise.resolve([{}, this.thread]);
     }
 
     const packet = {
       to: this._threadActor,
       type: "attach",
       options,
     };
     return this.request(packet).then(response => {
-      if (!response.error) {
-        this.thread = new ThreadClient(this, this._threadActor);
-        this.client.registerClient(this.thread);
-      }
-      onResponse(response, this.thread);
+      this.thread = new ThreadClient(this, this._threadActor);
+      this.client.registerClient(this.thread);
       return [response, this.thread];
     });
   },
 
   /**
    * Detach the client from the tab actor.
-   *
-   * @param function onResponse
-   *        Called with the response packet.
    */
   detach: DebuggerClient.requester({
     type: "detach"
   }, {
     before: function(packet) {
       if (this.thread) {
         this.thread.detach();
       }
@@ -135,28 +122,26 @@ TabClient.prototype = {
     url: arg(0)
   }),
 
   /**
    * Reconfigure the tab actor.
    *
    * @param object options
    *        A dictionary object of the new options to use in the tab actor.
-   * @param function onResponse
-   *        Called with the response packet.
    */
   reconfigure: DebuggerClient.requester({
     type: "reconfigure",
     options: arg(0)
   }),
 
   listWorkers: DebuggerClient.requester({
     type: "listWorkers"
   }),
 
-  attachWorker: function(workerTargetActor, onResponse) {
-    return this.client.attachWorker(workerTargetActor, onResponse);
+  attachWorker: function(workerTargetActor) {
+    return this.client.attachWorker(workerTargetActor);
   },
 };
 
 eventSource(TabClient.prototype);
 
 module.exports = TabClient;
--- a/devtools/shared/client/worker-client.js
+++ b/devtools/shared/client/worker-client.js
@@ -1,21 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {DebuggerClient} = require("devtools/shared/client/debugger-client");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const eventSource = require("devtools/shared/client/event-source");
 loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
 
-const noop = () => {};
-
 function WorkerClient(client, form) {
   this.client = client;
   this._actor = form.from;
   this._isClosed = false;
   this._url = form.url;
 
   this._onClose = this._onClose.bind(this);
 
@@ -50,56 +47,43 @@ WorkerClient.prototype = {
       if (this.thread) {
         this.client.unregisterClient(this.thread);
       }
       this.client.unregisterClient(this);
       return response;
     },
   }),
 
-  attachThread: function(options = {}, onResponse = noop) {
+  attachThread: function(options = {}) {
     if (this.thread) {
       const response = [{
         type: "connected",
         threadActor: this.thread._actor,
         consoleActor: this.consoleActor,
       }, this.thread];
-      DevToolsUtils.executeSoon(() => onResponse(response));
       return response;
     }
 
     // The connect call on server doesn't attach the thread as of version 44.
     return this.request({
       to: this._actor,
       type: "connect",
       options,
     }).then(connectResponse => {
-      if (connectResponse.error) {
-        onResponse(connectResponse, null);
-        return [connectResponse, null];
-      }
-
       return this.request({
         to: connectResponse.threadActor,
         type: "attach",
         options,
       }).then(attachResponse => {
-        if (attachResponse.error) {
-          onResponse(attachResponse, null);
-        }
-
         this.thread = new ThreadClient(this, connectResponse.threadActor);
         this.consoleActor = connectResponse.consoleActor;
         this.client.registerClient(this.thread);
 
-        onResponse(connectResponse, this.thread);
         return [connectResponse, this.thread];
       });
-    }, error => {
-      onResponse(error, null);
     });
   },
 
   _onClose: function() {
     this.removeListener("close", this._onClose);
 
     if (this.thread) {
       this.client.unregisterClient(this.thread);
--- a/devtools/shared/gcli/source/lib/gcli/util/host.js
+++ b/devtools/shared/gcli/source/lib/gcli/util/host.js
@@ -183,31 +183,24 @@ exports.script.useTarget = function(tgt)
           }
 
           exports.script.onOutput(ev);
         }
       });
 
       consoleActor = target._form.consoleActor;
 
-      var onAttach = function(response, wcc) {
-        webConsoleClient = wcc;
-
-        if (response.error != null) {
-          reject(response);
-        }
-        else {
+      var listeners = [ 'PageError', 'ConsoleAPI' ];
+      client.attachConsole(consoleActor, listeners)
+        .then(([response, wcc]) => {
+          webConsoleClient = wcc;
           resolve(response);
-        }
-
-        // TODO: add _onTabNavigated code?
-      };
-
-      var listeners = [ 'PageError', 'ConsoleAPI' ];
-      client.attachConsole(consoleActor, listeners, onAttach);
+        }, response => {
+          reject(response);
+        });
     });
   });
 };
 
 /**
  * Execute some JavaScript
  */
 exports.script.evaluate = function(javascript) {
--- a/devtools/shared/webconsole/test/common.js
+++ b/devtools/shared/webconsole/test/common.js
@@ -47,26 +47,26 @@ function attachConsoleToTab(listeners, c
 }
 function attachConsoleToWorker(listeners, callback) {
   _attachConsole(listeners, callback, true, true);
 }
 
 var _attachConsole = async function(
   listeners, callback, attachToTab, attachToWorker
 ) {
-  function _onAttachConsole(state, response, webConsoleClient) {
-    if (response.error) {
-      console.error("attachConsole failed: " + response.error + " " +
-                    response.message);
-    }
-
+  function _onAttachConsole(state, [response, webConsoleClient]) {
     state.client = webConsoleClient;
 
     callback(state, response);
   }
+  function _onAttachError(state, response) {
+    console.error("attachConsole failed: " + response.error + " " +
+                  response.message);
+    callback(state, response);
+  }
 
   function waitForMessage(target) {
     return new Promise(resolve => {
       target.addEventListener("message", resolve, { once: true });
     });
   }
 
   let [state, response] = await connectToDebugger();
@@ -77,18 +77,18 @@ var _attachConsole = async function(
     return;
   }
 
   if (!attachToTab) {
     response = await state.dbgClient.getProcess();
     await state.dbgClient.attachTab(response.form.actor);
     const consoleActor = response.form.consoleActor;
     state.actor = consoleActor;
-    state.dbgClient.attachConsole(consoleActor, listeners,
-                                  _onAttachConsole.bind(null, state));
+    state.dbgClient.attachConsole(consoleActor, listeners)
+      .then(_onAttachConsole.bind(null, state), _onAttachError.bind(null, state));
     return;
   }
   response = await state.dbgClient.listTabs();
   if (response.error) {
     console.error("listTabs failed: " + response.error + " " +
                   response.message);
     callback(state, response);
     return;
@@ -115,22 +115,22 @@ var _attachConsole = async function(
       await tabClient.attachWorker(workerTargetActor);
     if (!workerClient || workerResponse.error) {
       console.error("attachWorker failed. No worker client or " +
                     " error: " + workerResponse.error);
       return;
     }
     await workerClient.attachThread({});
     state.actor = workerClient.consoleActor;
-    state.dbgClient.attachConsole(workerClient.consoleActor, listeners,
-                                  _onAttachConsole.bind(null, state));
+    state.dbgClient.attachConsole(workerClient.consoleActor, listeners)
+      .then(_onAttachConsole.bind(null, state), _onAttachError.bind(null, state));
   } else {
     state.actor = tab.consoleActor;
-    state.dbgClient.attachConsole(tab.consoleActor, listeners,
-                                   _onAttachConsole.bind(null, state));
+    state.dbgClient.attachConsole(tab.consoleActor, listeners)
+      .then(_onAttachConsole.bind(null, state), _onAttachError.bind(null, state));
   }
 };
 
 function closeDebugger(state, callback) {
   const onClose = state.dbgClient.close();
 
   state.dbgClient = null;
   state.client = null;