JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.js"></script>
<div id="app">
  <button type="button" @click="fetchTodos.execute">Load todos</button>
  <div v-if="!fetchTodos.isCalled">Click button to load todos</div>
  <div v-if="fetchTodos.isPending">Loading todos...</div>

  <ul v-if="fetchTodos.resolvedWithSomething">
      <li v-for="todo in todos">
          {{todo.text}}
      </li>
  </ul>

  <div v-if="fetchTodos.resolvedWithEmpty">
      There are no todos.
  </div>

  <catch-async-error :method="fetchTodos">
      <div v-if="fetchTodos.rejectedWith">
          Could not load todos due to an error. Details: {{fetchTodos.rejectedWith.message}}
      </div>
  </catch-async-error>
</div>

JavaScript

'use strict'

var blockRegex = /^(address|blockquote|body|center|dir|div|dl|fieldset|form|h[1-6]|hr|isindex|menu|noframes|noscript|ol|p|pre|table|ul|dd|dt|frameset|li|tbody|td|tfoot|th|thead|tr|html)$/i

function isBlockLevel(name) {
  return blockRegex.test(name)
}

Vue.use({
  install: function(Vue, options) {
    options = options || {}
    options.getComputedName = options.getComputedName || function (vm, funcName) {
      var withoutPrefix = funcName.replace(/^(fetch|get|load)/, '')
      return withoutPrefix.slice(0, 1).toLowerCase() + withoutPrefix.slice(1)
    }

    function isEmpty(val) {
      if (Array.isArray(val)) {
        return val.length === 0
      } else if (typeof val === 'object' && val !== null) {
        return Object.keys(val).length === 0
      } else if (val === null) {
        return true
      } else {
        return false
      }
    }

    function isFunction(func) {
      return typeof func === 'function'
    }

    function wrapMethod(func, vm, funcName) {
      function wrapped() {
        var args = [].slice.call(arguments)

        vm[funcName].isCalled = true
        vm[funcName].isPending = true
        vm[funcName].isResolved = false
        vm[funcName].isRejected = false
        vm[funcName].resolvedWith = null
        vm[funcName].resolvedWithSomething = false
        vm[funcName].resolvedWithEmpty = false
        vm[funcName].rejectedWith = null

        try {
          var result = func.apply(vm, args)
          if (result && result.then) {
            vm[funcName].promise = result.then(function(res) {
              vm[funcName].isPending = false
              vm[funcName].isResolved = true
              vm[funcName].resolvedWith = res

              var empty = isEmpty(res)
              vm[funcName].resolvedWithEmpty = empty
              vm[funcName].resolvedWithSomething = !empty

              return res
            }).catch(function(err) {
              vm[funcName].isPending = false
             ...