JSFiddle - React, Tailwind, and code Playground

by Simon Herteby

HTML

<div id="vue">
  <input v-model="string">
  <input v-model="string2">
  <wait :for="one">
    <div>
      {{one}}
    </div>
  </wait>
  <hr>
  <wait :for="[one, two]">
    <ul slot-scope="[one, two]">
      <li>{{one}}</li>
      <li>{{two}}</li>
    </ul>
  </wait>
  <hr>
  <wait :for="fail">
    <div slot-scope="fail">
      {{fail}}
    </div>
  </wait>
  <hr>
  <wait :for="[one, two, fail]">
    <ul slot-scope="[one, two, fail]">
      <li>{{one}}</li>
      <li>{{two}}</li>
      <li>{{fail}}</li>
    </ul>
  </wait>
</div>

Vue

const Spinner = {
	template:`<div>LOADING</div>`
}
const ErrorDisplay = {
	template:`
  	<div class="error">
    	<h2>{{header}}</h2>
      <p>{{body}}</p>
    </div>
  `,
  props:{
  	error:{
    	type:Error,
      required:true
    }
  },
  computed:{
  	header(){
    	return this.error.message
    },
    body(){
    	
    }
  }
}
Vue.component('wait', {
  render(h){
  	if(this.error){
    	return h(ErrorDisplay, {props:{error:this.error}})
    } else if(this.resolved) {
    	if(!this.$scopedSlots.default){
      	throw new Error('Missing scoped slot')
      }
    	return this.$scopedSlots.default(this.result)
    } else if(this.spinner){
    	return h(Spinner)
    }
  },
	props:{
  	for:{
    	validator: prop => {
      	if(prop instanceof Array){
        	return !prop.find(item => typeof item.then !== 'function')
        } else {
        	return typeof prop.then === 'function'
        }
      }
    },
    spinner:{
    	type:Boolean,
      default:false
    }
  },
  data(){
  	return {
    	resolved:false,
    	result:null,
      error:null
    }
  },
  computed:{
  	promise(){
    	return this.for instanceof Array ? Promise.all(this.for) : this.for
    }
  },
  watch:{
  	promise:{
    	handler(promise){
      	this.resolved = false
        this.result = null
        this.error = null
        promise.then(result => {
        	if(promise === this.promise){
          	this.resolved = true
          	this.result = result
          }
        }).catch(error => {
        	if(promise === this.promise){
          	this.error = error
          }
        })
      },
      immediate:true
    }
  }
})

function sleep(ms){
	return new Promise(resolve => setTimeout(resolve, ms))
}

new Vue({
	el:'#vue',
  data(){
  	return {
    	string:'foo',
      string2:'bar'
    }
  },
  computed:{
  	async one(){
    	return 'ONE'
    },
  	async two(){
    	const {string, string2} = this
      await sleep(1000)
      return 'TWO ' + string + string2
    },
    async fail(){
   ...