Search code examples
dartdart-htmldart-async

Server callback happen 4 times instead of 1


I noticed the below happen upon the execution of my call of the sever, what is the wrong thing here!

 getItems(){
    print('getItems');
    request = new HttpRequest();
    request.onReadyStateChange.listen(onData_getItems);

    request.open('POST', host+'/getItems');
    request.send(' ');  
}

onData_getItems(_){
   print('call');  // --> printed 4 times!!

if (request.readyState == HttpRequest.DONE && request.status == 200) { 
    print('Data loaded successfully..';
    
    print(request.responseText);   // --> printed 1 time!!
    for(Map item in JSON.decode(request.responseText)){
         LineItem..children.add(new OptionElement(value: item['Code'], data: item['Name']));
    }         
} 
else if (request.readyState == HttpRequest.DONE &&
  request.status == 0) { 
     print("could not connect to server, contact your admin");  
}
else print('something else');    // --> printed 3 times

}

the above is executed in my custom element:

class getPurchaseLines extends HtmlElement {
    static final tag = 'get-POlines';
    factory getPurchaseLines() => new Element.tag(tag);

    var shadow, LineItem, LineQty, Lineprice, clsBtn;

    getPurchaseLines.created() : super.created() {    
          shadow = this.createShadowRoot();      

         LineItem = new SelectElement()
               ..children.add(new OptionElement(value: '0', data:'Stock Code'));

          LineQty = new InputElement()..type='number'..placeholder='Qty'..style.width='50px';   
          Lineprice = new InputElement()..type='number'..placeholder='price'..style.width='50px';

          LineQty.onKeyUp.listen((e){if(LineQty.checkValidity() == false)LineQty.value='0';});
          Lineprice.onKeyUp.listen((e){if(Lineprice.checkValidity() == false)Lineprice.value='0';});

   shadow.host
            ..style.position='relative'
            ..style.display='inline-block'
            ..style.verticalAlign = 'top'
            ..style.backgroundColor = '#ffffff'
            ..style.boxShadow = '1px 1px 5px #333333'
            ..style.boxSizing = 'border-box'
            ..style.marginTop='2px'
            ..style.padding = '4px'
            ..style.paddingRight='30px'
            ..style.borderRadius='2px'
            ..style.fontSize='14px'  
            ..style.transition = 'all 0.2s ease-in'; 
   clsBtn
       ..onClick.listen((e)=> this.remove())
       ..style.position='absolute'
       ..style.right = '5px'
       ..style.top = '5px'
       ..style.color = 'black'
       ..style.cursor = 'pointer'
       ..style.zIndex = '1'
       ..style.fontSize = '16px'
       ..style.fontWeight = 'solid'  
       ..classes.add('ion-close-circled')
       ..text='x'
       ;

    shadow.nodes..add(LineItem)..add(LineQty)..add(Lineprice)..add(clsBtn);
   }

which is called in my function as below:

   Future fonixFuture() => new Future.value(true);                 
       for (var line in orderLines){               
               fonixFuture()
                     .then((_)=> LineDisplay = new getPurchaseLines())
                     .then((_)=> LineDisplay.getItems())
                     .then((_)=> this.parent.nodes.add(LineDisplay))
                     .then((_)=>print(this.parent.nodes));
            }

and only the last element (LineDisplay) shows the correct getItem() results. for example if I've 4 lines, the LineDisplay is:

  1. printed "call" 16 times in the counsle
  2. display the element LineDisplay 4 times in the browser
  3. the items field in the first 3 'LineDisplay' is null, while the last one shows the items executed correctly.

Attached illustration of the issue.

issue illustration

Update

After receiving answer, the 4 responses issue had been fixed, but still the output received in the last element only! the updated code is:

    return Future.forEach(orderLines, (ol) {
         return fonixFuture()
             .then((_)=>print(ol))
             .then((_)=> LineDisplay = new getPurchaseLines())
             .then((_)=> LineDisplay..getItems())
             .then((_)=> this.parent.nodes.add(LineDisplay))
             .then((_)=>print(this.parent.nodes));      
         });

the custom element is:

part of fonix_client_library;

class getPurchaseLines extends HtmlElement {
  static final tag = 'get-POlines';
  factory getPurchaseLines() => new Element.tag(tag);

  var shadow, LineItem, LineQty, Lineprice;

  getPurchaseLines.created() : super.created() {    
      shadow = this.createShadowRoot();      
      LineItem = new SelectElement()
        ..children.add(new OptionElement(value: '0', data:'Stock Code'));

      LineQty = new InputElement()..type='number'..placeholder='Qty'..style.width='50px';   
      Lineprice = new InputElement()..type='number'..placeholder='price'..style.width='50px';

      LineQty.onKeyUp.listen((e){if(LineQty.checkValidity() == false)LineQty.value='0';});
      Lineprice.onKeyUp.listen((e){if(Lineprice.checkValidity() == false)Lineprice.value='0';});

      shadow.host
            ..style.position='relative'
            ..style.display='inline-block'
            ..style.verticalAlign = 'top'
            ..style.backgroundColor = '#ffffff'
            ..style.boxShadow = '1px 1px 5px #333333'
            ..style.boxSizing = 'border-box'
            ..style.marginTop='2px'
            ..style.padding = '4px'
            ..style.paddingRight='30px'
            ..style.borderRadius='2px'
            ..style.fontSize='14px'  
            ..style.transition = 'all 0.2s ease-in'; 
   shadow.nodes..add(LineItem)..add(LineQty)..add(Lineprice);  
  }

 getItems(){
   print('getItems');
   request = new HttpRequest();
   request.onReadyStateChange.listen(onData_getItems);

   request.open('POST', host+'/getItems');
   request.send(' '); 
 }

 onData_getItems(_){
    if (request.readyState != HttpRequest.DONE) return;
    else
      print('responce recieved..: ${request.responseText}');

    if (request.readyState == HttpRequest.DONE && request.status == 200) { 
      fonixFooter.innerHtml='Items retreived..';
    
      print('responce after checking ifItemsretrieved..: ${request.responseText}');
    
          for(Map item in JSON.decode(request.responseText)){
                 LineItem..children.add(new OptionElement(value: item['Code'], data: item['Name']));
                 
          }

} 
   else if (request.readyState == HttpRequest.DONE &&
  request.status == 0) { 
    print('no server..');
  }
  } 
}

the second screen shoots shows the updated output illustration 2


Solution

  • You listen to a stream, there it is expected to get more than one event.

    http://www.w3schools.com/jsref/prop_doc_readystate.asp says

    This property returns one of four values:

    • uninitialized - Has not started loading yet
    • loading - Is loading
    • interactive - Has loaded enough and the user can interact with it
    • complete - Fully loaded

    If you are not interested in uninitialized, loading, interactive you can just check

    onData_getItems(_){
      if (request.readyState != HttpRequest.DONE) return;
      print('call');  // --> printed 1 time
    

    Update (2nd issue)

    Executing async functions in a forEach doesn't work as you probably expect

    instead of

    for (var line in orderLines){ 
    

    use

    return Future.forEach(orderlines, (ol) {
        return fonixFuture()
        .then((_)=> LineDisplay = new getPurchaseLines())
        .then((_)=> LineDisplay.getItems())
        .then((_)=> this.parent.nodes.add(LineDisplay))
        .then((_)=>print(this.parent.nodes));      
    });
    

    It looks a bit weird that you artifically introduce async execution without an obvious reason

    Future fonixFuture() => new Future.value(true);
    

    therefore it's hard for me to tell how to integrate the above code snippet in your app code.
    I suggest you create a new question if issues remain.

    Update

    return Future.forEach(orderLines, (ol) {
      print(ol);
      LineDisplay = new getPurchaseLines();
      return LineDisplay.getItems()
      .then((success) {
        if(success) {
          parent.nodes.add(LineDisplay);
          print(this.parent.nodes);      
        } else {
          print('getItems() failed!');
        } // <== added
      });
    });
    
    
    Future getItems(){
      print('getItems');
      request = new HttpRequest();
      Completer completer = new Completer();
      request.onReadyStateChange.listen((_) => onData_getItems(completer)); // <== changed
    
      request.open('POST', host+'/getItems');
      request.send(' '); 
      return completer.future;
    }
    
    onData_getItems(Completer completer) {
      if (request.readyState != HttpRequest.DONE) {
        return;
      } else {
        print('responce recieved..: ${request.responseText}');
      }
    
      if (request.status == 200) { 
        fonixFooter.innerHtml='Items retreived..';
    
        print('responce after checking ifItemsretrieved..: ${request.responseText}');
        for(Map item in JSON.decode(request.responseText)){
          LineItem..children.add(new OptionElement(value: item['Code'], data: item['Name']));
        }
        completer.complete(true);
      } else if (request.readyState == HttpRequest.DONE && request.status == 0) { 
        print('no server..');
        completer.complete(false);
        // or completer.completeError('failed');
      }
    }