I'm building abstraction layer for keepassxc webextension. It's using redux-saga channels to make look chrome messaging synchronous. It's working (un)surprisingly well. However I want to completely abstract redux-saga, the way it will look like normal function returning Promise.
tl;dr
KeePassXC-browser will be browser extension that will allow retrieving passwords stored in KeePassXC app from the browser.
There are two possible communication protocols: HTTP and NativeClient. So I decided to use typescript interface and depending on communication protocol there will be two classes that implements this interface.
Interface:
interface Keepass {
getDatabaseHash(): Promise<string>;
getCredentials(origin: string, formUrl: string): Promise<KeepassCredentials[]>;
associate(): Promise<KeepassAssociation>;
isAssociated(dbHash: string): Promise<boolean>;
}
First implementation representing HTTP communication protocol is using fetch api, which is already Promise based, so implementation is straight forward and 100% conformed to this interface.
Second implementation representing NativeClient protocol is using redux-saga (effects and channels) to make asynchronous messaging look like synchronous function call. It's bit complicated, but works pretty well and covers edge cases, that would be hard to handle any other way, because native messaging is protocol based on standard input and standard output streams, so request and responses can be interleaved, out of order etc...
The actual problem I'm failing to solve, is that second implementation is not implementing interface, because it's generators not Promises.
Basically would like to convert (wrap) saga iterator function with function returning Promise. There is nice co library that basically does this for normal generators. But doesn't seem to work with redux saga.
function* someGenerator() {
const state = yield select(); // execution freeze here when called from wrapper
const result = yield call(someEffect);
return result;
}
function wrapper() {
return co(someGenerator); // returns Promise
}
Is this possible? If so, what I'm doing wrong?
Redux-saga is based on generator functions for special reason - to allow split asynchronous actions to separated yielded parts and manage them from one endpoint, which located at internal saga process-manager. Instead, in general case, Promise is a thing-in-self, and can't be partial executed. In other simplified words, Promises manage control flow in which them are located, and generators are managed by outer control flow.
yield select(); // execution freeze here when called from wrapper
Your main misconception is to assume that select actual perform some async operation. No, it just pauses function somegenatator on that point and transfers control to redux-saga engine, which knows that to do with returned value, and maybe state async process (Maybe no - it does not matter)
When process is done, saga engine resumes generator, and passes return value to it.
You can easily see it in source code of select (https://github.com/redux-saga/redux-saga/blob/master/src/internal/io.js#L139 ). It just returns an object with some structure, which can be understood by saga engine, then engine perform real action, and calls your generator in generatorName.next(resultValue) format.
UPD. Pure theoretically, you can wrap it to re-assignable promise, but it's not usable case
// Your library code
function deferredPromise() {
let resolver = null;
const promise = new Promise(resolve => (resolver = resolve));
return [
resolver,
promise
];
}
function generateSomeGenerator() {
let [ selectDoneResolve, selectDonePromise ] = deferredPromise();
const someGenetator = function* () {
const state = yield select(); // execution freeze here when called from wrapper
const [newSelectDoneResolve, newSelectDonePromise] = deferredPromise();
selectDoneResolve({
info: state, nextPromise: newSelectDonePromise
});
selectDoneResolve = newSelectDoneResolve;
selectDonePromise = newSelectDonePromise;
const result = yield call(someEffect);
return result;
}
return {
someGenetator,
selectDonePromise
};
}
const { someGenetator: someGenetatorImpl, selectDonePromise } = generateSomeGenerator();
export const someGenetator = someGenetatorImpl;
// Wrapper for interface
selectDonePromise.then(watchDone)
function watchDone({ info, nextPromise }) {
// Do something with your info
nextPromise.then(watchDone);
}
Related
I am looking for existing solutions to match dynamic parameters with HttpCore. What I have in mind is something similar to constraints in ruby on rails, or dynamic parameters with sails (see here for example).
My objective is to define a REST API where I could easily match requests like GET /objects/<object_id>.
To give a little bit of context, I have an application that creates an HttpServer using the following code
server = ServerBootstrap.bootstrap()
.setListenerPort(port)
.setServerInfo("MyAppServer/1.1")
.setSocketConfig(socketConfig)
.registerHandler("*", new HttpHandler(this))
.create();
And the HttpHandler class that matches the requested URI and dispatches it to the corresponding backend method:
public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context) {
String method = request.getRequestLine().getMethod().toUpperCase(Locale.ROOT);
// Parameters are ignored for the example
String path = request.getRequestLine().getUri();
if(method.equals("POST") && path.equals("/object/add") {
if(request instanceof HttpEntityEnclosingRequest) {
addObject(((HttpEntityEnclosingRequest)request).getEntity())
}
[...]
For sure I can replace path.equals("/object/add") by something more sophisticated with RegEx to match these dynamic parameters, but before doing so I'd like to know if I am not reinventing the wheel, or if there is an existing lib/class I didn't see in the docs that could help me.
Using HttpCore is a requirement (it is already integrated in the application I am working on), I know some other libraries provide high-level routing mechanisms that support these dynamic parameters, but I can't really afford switching the entire server code to another library.
I am currently using httpcore 4.4.10, but I can upgrade to a newer version of this might help me.
At present HttpCore does not have a fully featured request routing layer. (The reasons for that are more political than technical).
Consider using a custom HttpRequestHandlerMapper to implement your application specific request routing logic.
final HttpServer server = ServerBootstrap.bootstrap()
.setListenerPort(port)
.setServerInfo("Test/1.1")
.setSocketConfig(socketConfig)
.setSslContext(sslContext)
.setHandlerMapper(new HttpRequestHandlerMapper() {
#Override
public HttpRequestHandler lookup(HttpRequest request) {
try {
URI uri = new URI(request.getRequestLine().getUri());
String path = uri.getPath();
// do request routing based on the request path
return new HttpFileHandler(docRoot);
} catch (URISyntaxException e) {
// Provide a more reasonable error handler here
return null;
}
}
})
.setExceptionLogger(new StdErrorExceptionLogger())
.create();
I'm implementing an interface that returns a DeviceInformationCollection. The implementation can time out (or fail), in which case I would like to return an empty collection. This is to allow clients of the interface to always iterate over the returned collection, regardless of whether it succeeded or not, e.g.
auto&& devices{ co_await MyType::GetDevicesAsync() };
for (auto&& device : devices)
{
// Do crazy stuff with 'device'
}
However, I cannot figure out, how to construct an empty DeviceInformationCollection. The following code 'works', but causes undefined behavior when clients use the code above:
IAsyncOperation<DeviceInformationCollection> MyType::GetDevicesAsync()
{
// Doing Guru Meditation
// ...
co_return { nullptr };
}
My current workaround is to return an IVector<DeviceInformation> instead, and copy the items of the internal DeviceInformationCollection into the vector on success. That's both tedious as well as inefficient. I'd much rather just return the DeviceInformationCollection as-is, and construct an empty collection on failure.
Is there a way to do this?
Officially, this is not supported as the DeviceInformationCollection class does not provide a way to create an empty instance of itself. Unless you can find some function in the Windows.Devices.Enumeration API that does this for you you're out of luck.
Unofficially, we can observe that the default interface for the DeviceInformationCollection class is IVectorView. This means that this interface represents the class on the ABI. So you can play tricks with this knowledge but in general, this is very dangerous because APIs that accept a DeviceInformationCollection as input may assume that its implementation is exclusive and thus rely on some internal layout that you may not be aware of. Better to return IVectorView every time in a polymorphic and safe manner. Something like this:
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Devices::Enumeration;
IAsyncOperation<IVectorView<DeviceInformation>> Async()
{
DeviceInformationCollection devices = co_await // ... some async call
if (devices)
{
co_return devices;
}
// Returns empty IVectorView...
co_return single_threaded_observable_vector<DeviceInformation>().GetView();
}
int main()
{
for (auto&& device : Async().get())
{
printf("%ls\n", device.Name().c_str());
}
}
I have a simple program that consumes IMDB api, I'm getting the result, but it was shown as error because the result is not a structured json.
MovieService.ts
export class MovieService {
constructor(private http:HttpClient) { }
getMovie(movie:string){
return this.http.get(this.generateURL(movie));
}
private generateURL(movie:string){
return "https://v2.sg.media-imdb.com/suggests/titles/"+movie.charAt(0)+"/"+movie+".json?callback=imdb$"+movie;
}
}
addmovie.component.ts
private _filterMovies(value: string) {
this.movieService.getMovie(value).subscribe(
movies => {
console.log(movies);
return movies;
}
);
}
ngOnInit() {
this.addMovieForm.get('movie').valueChanges.subscribe(val => {
this._filterMovies(val)
});
}
I'm getting error like
the response is of bad json. How can I format the json upon receiving? How to solve this? Any leads would be helpful.
The result is not JSON, but rather JSONP. It is essentially returning you a script that is trying to execute the callback method specified.
Instead of http.get() you should call http.jsonp(url, "imbdIgnoresThisParam").
However, according to this answer, the callback query string parameter is ignored by IMDB. The answer suggests dynamically creating the expected callback function, whose name contains the title for which you are searching. In that callback you could do a few different things.
Use the closure to call / set something in your MovieService. This will result in your call to the API throwing an error, as the Angular framework's callback will not be called as expect. You could ignore the error.
Try to call the expected Angular callback, ng_jsonp_callback_<idx>. This will prevent the API call from throwing, but it may not be reliable. The callback name is dynamic and increments with each jsonp() call. You could try to track the number of jsonp() calls in your app. And of course, the framework may change and break this solution. Concurrent calls to getMovie() may break, as the next one may step on the previous callback on the window. Use with caution!
In typescript, your getMovie() function and related helpers might look like so:
private imdbData: any = null;
private jsonpIdx = 0;
private setImdb(json: any) {
this.imdbData = json;
// or do whatever you need with this
}
getMovie(movie:string) {
// dynamically create the callback on the window.
let funcName = `imdb$${movie}`;
window[funcName] = (json: any) => {
// use the closure
this.setImdbData(json);
// or try to call the callback that Angular is expecting.
window[`ng_jsonp_callback_${this.jsonpIdx++}`](json);
}
// go get your data!
let url = this.generateURL(movie)
return this.http.jsonp(url, "ignored").subscribe((json) => {
// this happens if you successfully trigger the angular callback
console.log(json);
}, (err) => {
// this happens if the angular callback isn't called
console.log(this.imdbData); // set in closure!
});
}
Edit for Angular 4
For Angular 4, it looks like you will need to import the JsonpModule along with the HttpModule. Then, you'd inject jsonp just like you'd inject http into your service. The call to IMDB becomes this.jsop.request(url).subscribe(...) and your dynamic callback name needs to change, too.
window[funcName] = (json: any) => {
// or try to call the callback that Angular is expecting.
window["__ng_jsonp__"][`__req${this.jsonpIdx++}`]["finished"](json);
}
I don't have an Angular 5 or 6 project immediately set up, so hard to say if there are any differences with the callback in those versions.
Sort of a hack, but hope it helps!
I have the following service in Angular2:
#Injectable()
export class MyService{
private myServiceUrl= ....
constructor(private http: Http) { }
getService(): Promise<MyObject> {
return this.http.get(this.myServiceUrl).map(response => response.json())
.toPromise();
}
}
And I have this function in one of my components:
myFunction(): any{
let toReturn: any;
this.myService.getService().then(result => toReturn = result);
console.log(toReturn);
return toReturn;
}
Basically, as you can see, I want to store in toReturn, the objects that it is returned by the promise of myService. I am looking to the rest call I have, and I am getting the proper json, but when I am trying to store it in the internal variable, I get undefinied in toReturn.
However, if I try this:
this.myService.getService().then(result=> console.log(result));
I am able to see the json I want.
I can do a function like this:
getService(address: string) {
this.myService.getService().then(result=> this.result= result);
}
But I prefer to make my function to return an object. What am I doing wrong?
Remember that getService is asynchronous, so when your code reaches the line
return toReturn;
The toReturn variable has not yet received the data coming from the server.
The cleanest way to deal with this is to return the promise itself, so that the calling code will extract the data when it arrives from the server:
myFunction(): Promise<MyObject>
{
//return the promise that will output data in the future
return this.myService.getService();
}
The calling code can access the data this way:
myFunction().then( result =>
this.result= result
)
You can even remove a step by getting rid of myFunction because it's just a thin wrapper around getService(). Instead the calling code can simply do:
this.myService.getService().then(result => this.result = result)
And that's it.
Addendum to address your comments:
You must understand two concepts about asynchronous ops and promises:
A promise is executed in a different thread, so the lines after the call to getService() are executed before the data arrives. That's why console.log() doesn't show you data at that point. That's also why trying to return the result there doesn't work
the only place in code where you can capture the result of a promise is .then(). There you can do whatever you want with the result, including storing it in any variable. Just remember point 1 above, and don't expect to have access to the result on the very next line because then() occurs later in time on a different thread.
assign the json that comes from that service (which returns a
promise), to an internal variable that I defined in the line above, it
is not working. How can store it to an internal variable and return it
You cannot.
Is it good to wrap the linq 2 sql query into the Task.Run method as shown below
var keywordlistquery = await Task.Run(() =>
{
using (DataContext context = new DataContext(connection))
{
context.ObjectTrackingEnabled = false;
return from keyword in context.GetTable<KeywordsList>()
select new
{
keyword.search_text,
keyword.search_keyword
};
}
});
Is the above code thread safe, will it have any issues while working on production.Is there any alternate better way of writing the above code.
A good answer here depends a lot on what the intent of the code is.
In general though, keep in mind that Linq to SQL technologies were built and then discontinued before native async and await patterns were implemented in .Net.
So, unless you are very comfortable with maintaining async tasks manually, it might be a good idea not to try use async with Linq to SQL at all. Odds are, you will not get much of a performance boost unless the server is expected to handle very high levels of request concurrency, but manually mucking around with async tasks is a fantastic way to introduce really hard to detect bugs that end up accidentally blocking request threads.
If you do need to handle async in code like this, there are a couple of solutions.
First understand that the code above creates a query, but doesn't execute it. What it returns is an IQuerable... basically, think of it as a SQL statement that hasn't been run. Linq to SQL will not run the query until a method like ToArray or ToList is called, or until it is used in a foreach loop or similar.
Also, it becomes difficult to work with anonymous types like this when you are using return statements. You will likely need to create DTO classes and use select projections to instantiate them
Second, you are wrapping the context in a using block (which is a good practice), but if you return the query before it is actually executed then the context gets disposed. The caller will get an IQueryable, but when it tries to use it you'll end up with an exception because the context has been disposed.
So.... there are two options here depending on if this code is intended to return actual data, or return just a query that the caller can then further modify.
Case 1) return data:
public async Task<object> DoThings(CancellationToken token)
{
var keywordlistquery = await Task.Run(() =>
{
using (var context = new DataClasses1DataContext())
{
context.ObjectTrackingEnabled = false;
return from keyword in context.GetTable<KeywordsList>()
select new
{
keyword.search_text,
keyword.search_keyword
};
}
}, token);
return keywordlistquery;
}
Note here that the method itself should be async, and you should always try to use a cancellation token when possible. This calls ToArray to force the query to execute now and return the data. Keep in mind though that this will return the WHOLE table. If the caller wants to supply a where clause or whatever, the code will still load all the data.
Case 2: return IQuerable
In case 2, you want your method to return just the query. This way, the caller can modify the query before it gets executed. This allows the caller to add statements to include a where clause or order the results or whatever; and have those statements included in the TSQL that gets generated.
In this case, the trick is that the caller must be in control of the lifespan of the data context, and since the method isn't actually executing results, it doesn't need to be async.
public async Task CallingMethod()
{
using (var context = new DataClasses1DataContext())
{
var token = new CancellationToken();
context.ObjectTrackingEnabled = false;
var query = DoThings(context);
var result = await Task.Run(() => query.ToArray(), token);
}
}
public IQueryable<object> DoThings(DataContext context)
{
var keywordlistquery = from keyword in context.GetTable<KeywordsList>()
select new
{
keyword.search_text,
keyword.search_keyword
};
return keywordlistquery;
}
As I mentioned before though, select new anonynous doesn't work that well in cases like this. It would be better to create a DTO class and select a new one of those, or return the whole table.