Wait while request is running - dart

Here is a problem. When I ran these code:
String responseText = null;
HttpRequest.getString(url).then((resp) {
responseText = resp;
print(responseText);
});
print(responseText);
In console:
{"meta":{"code":200},"data":{"username":"kevin","bio":"CEO \u0026 Co-founder of Instagram","website":"","profile_picture":"http:\/\/images.ak.instagram.com\/profiles\/profile_3_75sq_1325536697.jpg","full_name":"Kevin Systrom","counts":{"media":1349,"followed_by":1110365,"follows":555},"id":"3"}}
null
It running asynchronously. Is there JAVA way with synchronized method? That will be await while request is done?
I found only one tricky way to do it and its funny -- wait for three seconds:
handleTimeout() {
print(responseText);
}
const TIMEOUT = const Duration(seconds: 3);
new Timer(TIMEOUT, handleTimeout);
And of course it works with bugs. So any suggestions?
MattB way work well:
var req = new HttpRequest();
req.onLoad.listen((e) {
responseText = req.responseText;
print(responseText);
});
req.open('GET', url, async: false);
req.send();

First, I'm assuming you're using this as a client-side script and not server-side. Using HttpRequest.getString will strictly return a Future (async method).
If you absolutely must have a synchronous request, you can construct a new HttpRequest object and call the open method passing the named parameter: async: false
var req = new HttpRequest();
req.onLoad.listen((e) => print(req.responseText));
req.open('GET', url, async: false);
req.send();
However it is highly recommended that you use async methods for accessing network resources as a synchronous call like above will cause the script to block and can potentially make it appear as though your page/script has stopped responding on poor network connections.

Related

How to log the response for a dart shelf request

I'm using the Dart Shelf package and I need to log the response it sends.
I've managed to log the request but the response technique is less clear:
final handler = const shelf.Pipeline()
.addMiddleware(corsHeaders())
.addMiddleware(shelf.logRequests(
logger: (message, isError) =>
_logRequest(message, isError: isError)))
.addHandler((req) async {
final res = await Router().call(req);
return res;
});
There two parts to the question.
how do I log the headers.
is it possible to log the body.
I know there is an issue in that the response body can only be read once.
As some of the responses are likely to be large I need to filter the requests for which the body is logged.
The answer is a bit of Dart-fu. You have an anonymous function returning an anonymous function.
var handler = const Pipeline()
.addMiddleware(
(handler) => (request) async {
final response = await handler(request);
print(response.headers);
// you could read the body here, but you'd also need to
// save the content and pipe it into a new response instance
return response;
},
)
.addHandler(syncHandler);

How to call asynchronous function before event.respondWith in Service Worker?

its not work.
self.addEventListener('fetch', async (event) => {
const url = await localforage.getItem('url'); // url change if page changed
if(event.request.url === url){
event.respondWith(handleFetch(event));
}
});
const handleFetch = async (event) => {
....
}
if i move url to inside event.responseWith. its work, but do every request, but i need only match url to fetch in service worker if not match then do nothing.
self.addEventListener('fetch', async (event) => {
event.respondWith(handleFetch(event));
});
const handleFetch = async (event) => {
const url = await localforage.getItem('url'); // url change if page changed
if(event.request.url === url){
....
}
}
This is intentional. Your decision as to whether or not to call event.respondWith() needs to be done synchronously, within the top-level execution of your fetch handler.
This allows you to do things like examine the request URL and headers synchronously, but it does preclude you from performing asynchronous lookups against things like IndexedDB.
If you can't transition your criteria to use something synchronous, then your best option is to call event.respondWith() unconditionally, and when the criteria is not met, use return fetch(event.request) to come as close as you could to the "default" behavior you'd get if you didn't respond at all. (That's basically what you're doing in the second example.)

Service Workers and IndexedDB

In a simple JavaScript Service Worker I want to intercept a request and read a value from IndexedDB before the event.respondWith
But the asynchronous nature of IndexDB does not seem to allow this.
Since the indexedDB.open is asynchronous, we have to await it which is fine. However, the callback (onsuccess) happens later so the function will exit immediately after the await on open.
The only way I have found to get it to work reliably is to add:
var wait = ms => new Promise((r, j) => setTimeout(r, ms));
await wait(50)
at the end of my readDB function to force a wait until the onsuccess has completed.
This is completely stupid!
And please don't even try to tell me about promises. They DO NOT WORK in this circumstance.
Does anyone know how we are supposed to use this properly?
Sample readDB is here (all error checking removed for clarity). Note, we cannot use await inside the onsuccess so the two inner IndexedDB calls are not awaited!
async function readDB(dbname, storeName, id) {
var result;
var request = await indexedDB.open(dbname, 1); //indexedDB.open is an asynchronous function
request.onsuccess = function (event) {
let db = event.target.result;
var transaction = db.transaction([storeName], "readonly"); //This is also asynchronous and needs await
var store = transaction.objectStore(storeName);
var objectStoreRequest = store.get(id); //This is also asynchronous and needs await
objectStoreRequest.onsuccess = function (event) {
result = objectStoreRequest.result;
};
};
//Without this wait, this function returns BEFORE the onsuccess has completed
console.warn('ABOUT TO WAIT');
var wait = ms => new Promise((r, j) => setTimeout(r, ms));
await wait(50)
console.warn('WAIT DONE');
return result;
}
And please don't even try to tell me about promises. They DO NOT WORK in this circumstance.
...
...
...
I mean, they do, though. Assuming that you're okay putting the promise-based IndexedDB lookups inside of event.respondWith() rather than before event.respondWith(), at least. (If you're trying to do this before calling event.respondWith(), to figure out whether or not you want to respond at all, you're correct in that it's not possible, since the decision as to whether or not to call event.respondWith() needs to be made synchronously.)
It's not easy to wrap IndexedDB in a promise-based interface, but https://github.com/jakearchibald/idb has already done the hard work, and it works quite well inside of a service worker. Moreover, https://github.com/jakearchibald/idb-keyval makes it even easier to do this sort of thing if you just need a single key/value pair, rather than the full IndexedDB feature set.
Here's an example, assuming you're okay with idb-keyval:
importScripts('https://cdn.jsdelivr.net/npm/idb-keyval#3/dist/idb-keyval-iife.min.js');
// Call idbKeyval.set() to save data to your datastore in the `install` handler,
// in the context of your `window`, etc.
self.addEventListener('fetch', event => {
// Optionally, add in some *synchronous* criteria here that examines event.request
// and only calls event.respondWith() if this `fetch` handler can respond.
event.respondWith(async function() {
const id = someLogicToCalculateAnId();
const value = await idbKeyval.get(id);
// You now can use `value` however you want.
const response = generateResponseFromValue(value);
return response;
}())
});

How to mock HttpClientResponse to return a String

I am trying to write a test after refactoring to dart:io.HttpClient following https://flutter.io/networking/
Everything seems to work well up until
var responseBody = await response.transform(utf8.decoder).join();
The following test throws a NoSuchMethodError: The method 'join' was called on null.
MockHttpClient http = new MockHttpClient();
MockHttpClientRequest request = new MockHttpClientRequest();
MockHttpHeaders headers = new MockHttpHeaders();
MockHttpClientResponse response = new MockHttpClientResponse();
MockStream stream = new MockStream();
when(http.getUrl(Uri.parse('http://www.example.com/')))
.thenReturn(new Future.value(request));
when(request.headers)
.thenReturn(headers);
when(request.close())
.thenReturn(new Future.value(response));
when(response.transform(utf8.decoder))
.thenReturn(stream);
when(stream.join())
.thenReturn(new Future.value('{"error": {"message": "Some error"}}'));
I did see How to mock server response - client on server side, but that uses the http package, not dart:io.
I also tried https://github.com/flutter/flutter/blob/master/dev/manual_tests/test/mock_image_http.dart but that also returns a null.
Much thanks in advance!
The problem is that when you mock stream you actually need to implement a ton of different methods to get it to work properly. It is better to use a real Stream if you can like in the example in the flutter repo. To make sure your body is correctly set, use the utf8 encoder.
final MockHttpClientResponse response = new MockHttpClientResponse();
// encode the response body as bytes.
final List<int> body = utf8.encode('{"foo":2}');
when(response.listen(typed(any))).thenAnswer((Invocation invocation) {
final void Function(List<int>) onData = invocation.positionalArguments[0];
final void Function() onDone = invocation.namedArguments[#onDone];
final void Function(Object, [StackTrace]) onError = invocation.namedArguments[#onError];
final bool cancelOnError = invocation.namedArguments[#cancelOnError];
return new Stream<List<int>>.fromIterable(<List<int>>[body]).listen(onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError);
});

Dart: Synchronous post request

I would like to send a synchronous POST request from the client-side. According to the documentation we can use the 'async' named parameter:
https://www.dartlang.org/articles/json-web-service/#saving-objects-on-the-server
var url = "http://127.0.0.1:8080/programming-languages";
request.open("POST", url, async: false);
But the above example throws the following syntax error:
The keywords 'async', 'await', and 'yield' may not be used as identifiers in an asynchronous or generator function.
How can I send a synchronous POST request?
UPDATE (27 May, 20:23)
I found a workaround to solve this problem:
Future<String> deleteItem(String id) async {
final req = new HttpRequest()
..open('POST', 'server/controller.php')
..send({'action': 'delete', 'id': id});
// wait until the request have been completed
await req.onLoadEnd.first;
// oh yes
return req.responseText;
}
But I don't like the above solution because it doesn't seem elegant enough.
This is a known issue with this named parameter https://github.com/dart-lang/sdk/issues/24637
The solution is to use postFormData() instead of send(). For example:
final req = await HttpRequest
.postFormData(url, {'action': 'delete', 'id': id});
return req.responseText;
Future<String> deleteItem(String id) async {
final req = new HttpRequest()
..open('POST', 'server/controller.php')
..send({'action': 'delete', 'id': id});
// wait until the request have been completed
await req.onLoadEnd.first;
// oh yes
return req.responseText;
}
this one is the point, where sometimes you need "PUT" or "DELETE"

Resources