Aurelia JS - Making a synchronous HTTP request, to

2020-05-08 08:35发布

I'm working with the contact list tutorial:

... and I wanted to change it, so the application first starts with a "click me" button. Upon click on this button, a web request should be made, which should return JSON contact data. If the request is successful, the response should update the main data store of contacts, and the page should start with rendering the new contact list; if the request fails, the page should show the original (hardcoded) list of contacts.

Copy of the original contact list app can be found on https://gist.run/?id=c73b047c8184c052b4c61c69febb33d8 (Chrome only for now); while the changes I've made to implement the above are in:

This is what I tried doing - first, there are the changes for the start button (also in Aurelia JS - cannot navigate route parent with click (Route not found)?). * Then, in web-api.js there is a new setContactList function which should allow for change of the data container variable. * After the start "click me" button is clicked, app-clist.* is loaded. In app-clist.js, there is PHP code that constructs a new contact list, and since I have no easy way of uploading and running server PHP code, I send that PHP code to http://phpfiddle.org which processes it and returns the results (see also https://softwarerecs.stackexchange.com/questions/39075/web-service-for-sharing-and-serving-php-code/39078#39078) This is done in the constructor() function, so the very first time the object in app-clist.js loads (which is after the start button is clicked). If this web call succeeds, then setContactList is called to change the contact list.

So, here is the problem - the web calls all succeed, but they happen way too late - after the page has been rendered. After clicking the start button, first the page (with old contacts) is rendered; then I get an alert:

An embedded page at gist.host says:

{"result":"[{\"id\":\"1\",\"firstName\":\"Bob\",\"lastName\":\"Glass\",\"email\":\"bob@glass.com\",\"phoneNumber\":\"243-6593\"},{\"id\":\"2\",\"firstName\":\"Chad\",\"lastName\":\"Connor\",\"email\":\"chad@connor.com\",\"phoneNumber\":\"839-2946\"}]"}

... which means the web call succeeded, and then a second alert:

An embedded page at gist.host says:

setContactList 2 2

... which shows that the lengths of the received contacts array and the original one are the same, which means the update happened. Except, it happened to late.

This reminded me that HTTP calls in JavaScript tend to be asynchronous - i.e. they will just start the process, and will not block the rest of the code until they complete. Likely that is the case for aurelia-http-client, from where I used:

this.http.createRequest('https://phpfiddle.org/api/run/code/json')
 .asPost()
 .withHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8')
 .withContent("code="+encphpcode)
 .send()
 .then(response => {
     alert(response.response);
     console.log(response);
     var respobj = JSON.parse(response.response);
     var respdataArr = JSON.parse(respobj.result);
     this.api.setContactList(respdataArr);
 }).catch(err => {
     console.log(err);
 });

So for my concept - that I'm calling a service at the start of the lifetime of a page, which returns data that should be rendered on the page on first show - I'd have to have a synchronous call, which would block the execution of the constructor() in app-clist until it is complete (succeeds or fails), so that the data can be updated before the page rendering starts...

So my question is: how can I do a synchronous HTTP call with Aurelia JS? Alternatively, is it possible to something like my example here with asynchronous calls, and if so, how?

Here are some of the more relevant files for reference:

app-clist.html

<template>
  <require from="bootstrap/css/bootstrap.css"></require>
  <require from="./styles.css"></require>
  <require from="./contact-list"></require>

  <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
    <div class="navbar-header">
      <a class="navbar-brand" href="#">
        <i class="fa fa-user"></i>
        <span>Contacts</span>
      </a>
    </div>
  </nav>

  <div class="container">
    <div class="row">
      <contact-list class="col-md-4"></contact-list>
      <router-view name="chldrt" class="col-md-8"></router-view>
    </div>
  </div>
</template>

app-clist.js

import {WebAPI} from './web-api';
import {HttpClient} from 'aurelia-http-client';

// for multiline string, use backticks `` - ES6 template literals.
let phpcode = `
<?php
$outarr = array();

$tObj = new StdClass();
$tObj->{'id'} = '1';
$tObj->{'firstName'} = 'Bob';
$tObj->{'lastName'} = 'Glass';
$tObj->{'email'} = 'bob@glass.com';
$tObj->{'phoneNumber'} = '243-6593';
array_push($outarr, $tObj);
$tObj = new StdClass();
$tObj->{'id'} = '2';
$tObj->{'firstName'} = 'Chad';
$tObj->{'lastName'} = 'Connor';
$tObj->{'email'} = 'chad@connor.com';
$tObj->{'phoneNumber'} = '839-2946';
array_push($outarr, $tObj);

echo json_encode($outarr); 
?>
`; 

export class AppClist { // in gist, it is mistakenly still App
  static inject() { return [WebAPI, HttpClient]; }

  constructor(api, http){
    this.api = api;
    this.http = http;
    var phpcodesl = phpcode.replace(/(?:\r\n|\r|\n)/g, ' ');
    var encphpcode = encodeURIComponent(phpcodesl); // urlencode
    //alert(encphpcode); 
    // NOTE: gist.run due https will not allow loading from http
    //this.http.post("https://phpfiddle.org/api/run/code/json", "code="+encphpcode )
    //.then(response => {alert(response.response); console.log(response);}) // does not work
    // this does work:
    this.http.createRequest('https://phpfiddle.org/api/run/code/json')
     .asPost()
     .withHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8')
     .withContent("code="+encphpcode)
     .send()
     .then(response => {
         alert(response.response);
         console.log(response);
         var respobj = JSON.parse(response.response);
         var respdataArr = JSON.parse(respobj.result);
         this.api.setContactList(respdataArr);
     }).catch(err => {
         console.log(err);
     })
    ;
  }

  // no configureRouter(config, router){ here same as in app.js!
  /**/configureRouter(config, router){
    config.title = 'Contacts';
    config.map([
      // must include empty route '' here, else "Route not found" at start
      { route: ['','contacts'],      viewPorts: { chldrt: { moduleId: 'no-selection' } },   title: 'Select'},
      { route: 'contacts/:id',  viewPorts: { chldrt: { moduleId: 'contact-detail' } }, name:'contacts' }
    ]);

    this.router = router;
  }

}

app.html

<template>
  <require from="bootstrap/css/bootstrap.css"></require>
  <require from="./styles.css"></require>
  <require from="./contact-list"></require>

  <loading-indicator loading.bind="router.isNavigating || api.isRequesting"></loading-indicator>

  <router-view name="mainrt"></router-view>

</template>

app.js

import {WebAPI} from './web-api';

export class App {
  static inject() { return [WebAPI]; }

  constructor(api) {
    this.api = api;
  }

  configureRouter(config, router){
    config.title = 'App Contacts';
    config.map([
      { route: '',              viewPorts: { mainrt: { moduleId: 'btn-start' } },   title: 'Start'},
      { route: 'app-clist',     viewPorts: { mainrt: { moduleId: 'app-clist' }, chldrt: { moduleId: 'no-selection' } },   name: 'app-clist', title: 'C List'} //,
      //{ route: 'contacts',      viewPorts: { chldrt: { moduleId: 'no-selection' } },   title: 'Select'},
      //{ route: 'contacts/:id',  viewPorts: { chldrt: { moduleId: 'contact-detail' } }, name:'contacts' }
    ]);

    this.router = router;
  }
}

btn-start.html

<template>
  <div id="startbtn" click.trigger="goClist()">Click here to start!</div>
</template>

btn-start.js

import {WebAPI} from './web-api';
import { Router } from 'aurelia-router';
import {App} from './app';

export class BtnStart {
  static inject() { return [WebAPI, Router, App]; }

  constructor(api, router, app) {
    this.api = api;
    this.router = router;
    this.app = app;
  }

  goClist() {
    this.app.router.navigateToRoute("app-clist");
  }

}

web-api.js

let latency = 200;
let id = 0;

function getId(){
  return ++id;
}

let contacts = [
  {
    id:getId(),
    firstName:'John',
    lastName:'Tolkien',
    email:'tolkien@inklings.com',
    phoneNumber:'867-5309'
  },
  {
    id:getId(),
    firstName:'Clive',
    lastName:'Lewis',
    email:'lewis@inklings.com',
    phoneNumber:'867-5309'
  },
  {
    id:getId(),
    firstName:'Owen',
    lastName:'Barfield',
    email:'barfield@inklings.com',
    phoneNumber:'867-5309'
  },
  {
    id:getId(),
    firstName:'Charles',
    lastName:'Williams',
    email:'williams@inklings.com',
    phoneNumber:'867-5309'
  },
  {
    id:getId(),
    firstName:'Roger',
    lastName:'Green',
    email:'green@inklings.com',
    phoneNumber:'867-5309'
  }
];

export class WebAPI {
  isRequesting = false;

  setContactList(incontacts) {
    contacts = incontacts;
    alert("setContactList " + incontacts.length + " " + contacts.length);
    console.log("setContactList", incontacts, contacts);
  }


  getContactList(){
    this.isRequesting = true;
    return new Promise(resolve => {
      setTimeout(() => {
        let results = contacts.map(x =>  { return {
          id:x.id,
          firstName:x.firstName,
          lastName:x.lastName,
          email:x.email
        }});
        resolve(results);
        this.isRequesting = false;
      }, latency);
    });
  }

  getContactDetails(id){
    this.isRequesting = true;
    return new Promise(resolve => {
      setTimeout(() => {
        let found = contacts.filter(x => x.id == id)[0];
        resolve(JSON.parse(JSON.stringify(found)));
        this.isRequesting = false;
      }, latency);
    });
  }

  saveContact(contact){
    this.isRequesting = true;
    return new Promise(resolve => {
      setTimeout(() => {
        let instance = JSON.parse(JSON.stringify(contact));
        let found = contacts.filter(x => x.id == contact.id)[0];

        if(found){
          let index = contacts.indexOf(found);
          contacts[index] = instance;
        }else{
          instance.id = getId();
          contacts.push(instance);
        }

        this.isRequesting = false;
        resolve(instance);
      }, latency);
    });
  }
} 

1条回答
看我几分像从前
2楼-- · 2020-05-08 09:40

Well, I finally managed to make an async call which updates the GUI, as recommended by @LStarky; note that in order to do that, one must ensure ContactList class is a single instance class, so that there is only one property contacts whose binding updates the HTML GUI.

However, since all of this was a bit of a hacky guesswork, it would still be nice to get a proper answer from someone.

The single instance issue described in:

For reference, I've left the working example saved as a gist.run here:

... so basically, after the two alerts, the shown contacts will get updated with the data obtained from the PHP page.

查看更多
登录 后发表回答