Using ADO, if you use Command.Execute to execute a statement that does not return a result set, then that statement will be executed asynchronously. The function call returns immediately. I suppose this behaviour is defensible because the return value of the function is a recordset whose status you can query. However, if you use the flag adExecuteNoRecords and do not specify any of the flags adAsyncExecute or adAsyncFetch or adAsyncFetchNonBlocking, then you might expect the command to execute synchronously. In fact, it does not. The documentation was no help -- through experimentation, I found that I could achieve synchrony by calling NextRecordSet on the returned recordset. I could have just accepted the asynchronous behaviour and blocked until I received a completion event, had I not been using JScript that cannot interact with ADO events.
It seems questionable that Command.Execute executes asynchronously by default. But it is negligent that none of the ADO documentation even hints that this is the case, let alone lacks any advice on how to perform synchronous operations.