Proper way to err out of PromiseKit - promisekit

What is the proper way to throw an error from a function like this:
func fetch(by id: String, page: Int = 1) -> Promise<ProductReviewBase> {
// call api
guard let url = URL(string: "") else {
return Promise { _ in return IntegrationError.invalidURL }
}
return query(with: url)
}
I'm confused whether to make this a function that throws an error, or return a promise that returns an error. Thanks

I really hate interfaces that mix metaphors. If you are going to return a promise, then use the promise's error system. If you want more justification than my hatred, then visualize what it would look like at the call site:
do {
(try fetch(by: id))
.then {
// do something
}
.catch { error in
// handle error
}
}
catch {
// handle error
}
vs
fetch(by: id)
.then {
// do something
}
.catch { error in
// handle error
}
The latter looks a whole lot cleaner.
Here's the best way (IMO) to write your example function:
func fetch(by id: String, page: Int = 1) -> Promise<ProductReviewBase> {
guard let url = URL(string: "") else { return Promise(error: IntegrationError.invalidURL) }
return query(with: url)
}

Related

Swift Async/Await for C Callbacks

I am trying to convert some code from objective-c to swift and along with that use async/await as much as possible.
This code leverages a C library that uses callbacks with a void* context. There are parts of our code where we need to "wait" until the callback is completed. This can often be 2 different callbacks for success or failure.
Below is an example of the code
class Test
{
var sessionStarted: Bool = false
let sessionSemaphore = DispatchSemaphore(value: 0)
// We need the session to be available once this function returns
func startSession() throws
{
guard !sessionStarted else {
print("Session already started")
return
}
let callback: KMSessionEventCallback = { inSession, inEvent, inContext in
guard let session = inSession, let context = inContext else { return }
let testContext: Test = Unmanaged<Test>.fromOpaque(context).takeUnretainedValue()
testContext.handleSessionUpdate(session: session, event: inEvent)
}
let result = KMSessionAttach("SessionTest", &callback, Unmanaged.passUnretained(self).toOpaque())
if result != KM_SUCCESS {
print("Failed to attach session")
}
// Currently uses a semaphore to wait. =(
let dispatchResult = self.sessionSemaphore.wait(timeout: .now() + DispatchTimeInterval.seconds(30))
if dispatchResult == .timedOut {
print("Waited 30 seconds and never attached session")
}
}
private func handleSessionUpdate(session: KMSession, event: KMSessionEvent)
{
switch event
{
case KM_SESSION_ATTACHED:
self.sessionStarted = true
case KM_SESSION_TERMINATED:
fallthrough
case KM_SESSION_FAILED:
fallthrough
case KM_SESSION_DETACHED:
self.sessionStarted = false
default:
print("Unknown State")
}
self.sessionSemaphore.signal()
}
}
I am struggling to figure out how to use continuations here because I cannot capture any context within these C callbacks.

PromiseKit - Optional promise with reload first promise

I read all documentation but I don't response my question with successfull.
I have this scenario
I call the authenticated api point
If the response is 200 to 299 I fullfill
If the response is 401 I call the api for refresh the token
If i refreshed the API token I need to re-call the first api point (for recovery the needed data).
Can you help me with this problem?
Thanks
Use recursion for you needs. Using PromiseKit you can do awesome things. See my flow with refresh token request below. I expect that the code below is self-documented.
// Define here your errors
enum MyErrors: Swift.Error {
case unauthorized
case unknown
}
// Here is your primary function for retrieving remote data
func fetchRemoteData() {
self.primaryRequest()
.then { data in
return self.parsePrimaryRequest(response: data)
}
.done { parsedResult in
// do something here if all chaining is success
}
.recover { primaryRequestError in // this is your case for refresh token
if case MyErrors.unauthorized = primaryRequestError { // if not authorized
self.refreshTokenRequest()
.then { data in
return self.parseRefreshToken(data: data)
}
.done { token in
// do someting with token, save it in secure store (keychain)
self.fetchRemoteData()
}
.catch { refreshTokenError in
// refresh token request has failed
}
}
else {
throw primaryRequestError
}
}
.catch { primaryRequestError in
// here if error during request
}
}
func primaryRequest() -> Promise<Data> {
return Promise { seal in
// here you execure the request
// ...
// ...
let responseData = Data() // here you get data from URLSessionTask etc
let yourRequestStatusCode = 401 // take status code
let successCodes = Array(200...299) // define success codes
let authorizationErrorCode = 401 // define status code which requires performing refresh token request
if successCodes.contains(yourRequestStatusCode) {
return seal.fulfill(responseData)
} else if yourRequestStatusCode == authorizationErrorCode {
throw MyErrors.unauthorized
} else {
throw MyErrors.unknown
}
}
}
func parsePrimaryRequest(response: Data) -> Promise<String> {
return Promise { seal in
// here you parse response
// ...
// ...
let parsedObject = "Awesome result"
seal.fulfill(parsedObject)
}
}
func refreshTokenRequest() -> Promise<Data> {
return Promise { seal in
// here you execure the request to refresh token
// ...
// ...
let responseData = Data() // here you get data from URLSessionTask etc
let yourRequestStatusCode = 401 // take status code
let successCodes = Array(200...299) // define success codes for refresh token request
if successCodes.contains(yourRequestStatusCode) {
return seal.fulfill(responseData)
} else {
throw MyErrors.unknown
}
}
}
func parseRefreshToken(data: Data) -> Promise<String> {
return Promise { seal in
// here you parse response
// ...
// ...
let parsedObject = "00000000-00000000-00000000-00000000"
seal.fulfill(parsedObject)
}
}

Redux-Form - Array of errors

I'm stuck with handling array of objects that contain errors inside my react-native/redux application.
I have a handleResponse function that looks something like this:
function handleResponse (response) {
let status = JSON.stringify(response.data.Status)
let res = JSON.stringify(response)
if (Number(status) === 200) {
return res
} else {
throw new SubmissionError('Something happened')
}
}
But instead of plain text passed as argument to SubmissionError function - I want somehow to take out errors from array of objects.
Array of objects containing error messages looks like this:
{
ErrorMessages: [
{ ErrorMessage: 'foo' },
{ ErrorMessage: 'bar' }
]
}
How can I, for example, throw foo and bar errors?
You can't really throw two things at once, unless you wrap them in a Promise or do other tricks, I would just concatenate the errors together and do a single throw:
function handleResponse (res) {
if (Number(res.data.Status) === 200) {
return JSON.stringify(res)
}
const errors = res.ErrorMessages.map(e => e.ErrorMessage).join(', ')
throw new SubmissionError(`Some errors occurred: ${errors}.`)
}

getting an error using breeze - The 'structuralTypeName' parameter must be a 'string'

I am going through John Papa's SPA course on pluralsight and I am running into error that say The 'structuralTypeName' parameter must be a 'string' while using breeze. Here is the actual error that is being thrown
The code that is generating this error is metadataStore.getEntityType:
function extendMetadata() { names
var metadataStore = manager.metadataStore;
var types = metadataStore.getEntityType();
types.forEach(function(type) {
if (type instanceof breeze.EntityType) {
Set(type.shortName, type)
}
});
function set(resourceName, entityName) {
metadataStore.setEntityTypeForResourceName(resourceName, entityNames);
}
it is called by my prime function.
function prime() {
if (primePromise) return primePromise //if primePromise was loaded before, just return it
primePromise = $q.all([getLookups()])
.then(extendMetadata)
.then(success);
return primePromise;
function success() {
setLookups();
log('Primed the data');
}
I'm unsure what the problem is with the breeze call. Any insight into how to fix this? Thanks for your help community.
Nick
Here is the lookups query info:
function setLookups() {
var entityNames = {
personnel: 'Personnel',
cto: 'Cto',
kkeys: 'Kkey',
promotion: 'Promotion',
loa: 'Loa'
};
service.lookupCachedData = {
ctos: _getAllLocal(entityNames.cto, 'ctodate' )
kkeys: _getAllLocal(entityNames.kkeys, 'keystamp'),
promotions: _getAllLocal(entityNames.promotion, 'pdate'),
loas: _getAllLocal(entityNames.loa, 'lstrdte')
}
}
function _getAllLocal(resource, ordering) {
return EntityQuery.from(resource)
.orderBy(ordering)
.using(manager)
.executeLocally();
}
function getLookups() {
return EntityQuery.from('Lookups')
using(manager).execute()
.then(querySucceeded, _queryFailed)
function querySucceeded(data) {
log('Retrieved [Lookups] from remote data source', data, true);
return true;
}
}
function _queryFailed(error) {
var msg = config.appErrorPrefix + 'Error retrieving data from entityquery' + error.message;
logError(msg, error);
throw error;
}
You have to pass in a string to getEntityType. Sorry I missed that the first time through.
metadataStore.getEntityType('cto');
Also you are going to blow up when you are trying to call Set() function but the functions name is set() and also set is probably a keyword you aren't trying to override.

Angular how to deal with unavailable URLs requested by $http.get or $http.jsonp, which are executed by $q.all()

I've the following code:
eventResourcesCall = $http.jsonp('https://apicall/to/serverA');
eventsDetailsCall = $http.get('https://apicall/to/serverB');
$q.all([eventResourcesCall, eventsDetailsCall]).then(function(values){
//process data manipulation and merging
});
The problem is that serverA and ServerB might not be available sometimes, and when one of those are unavailable, the data processing code stops and I get an error similar to the one described below:
GET https://apicall/to/serverA?jsonp=angular.callbacks._0 404 (Not Found)
Can any one point me to a documentation or describe on the answer how to properly deal with unavailable URL requested by $http and executed by $q.all()?
What I would like to be able to do is to get an indication that the URL is not accessible and then avoid the data processing code abortion.
Thanks!
I would use indirect promises:
var d1 = $q.defer(), d2 = $q.defer();
function NetworkError(reason) { this.reason = reason; }
eventResourcesCall = $http.jsonp('https://apicall/to/serverA').then(
function(response) {
d1.resolve(response);
},
function(err) {
d1.resolve(new NetworkError(err));
}
);
eventsDetailsCall = $http.get('https://apicall/to/serverB').then(
function(response) {
d2.resolve(response);
},
function(err) {
d2.resolve(new NetworkError(err));
}
);
$q.all([d1, d2]).then(function(values){
var eventResources = values[0], eventsDetails = values[1];
if( eventResources instanceof NetworkError ) {
// handle error
}
else {
// eventResources is good, use it
}
// and so on...
});
So the indirect promises are allways resolved and the all() succeeds. But the resolution value may be of the special NetworkError class which signals the actual error in this request.
This is definitely bulky, but could be improved with some utility methods, e.g.:
function makeIndirectPromise(httpPromise) {
var ret = $q.defer();
httpPromise.then(
function(response) {
ret.resolve(response);
},
function(err) {
ret.resolve(new NetworkError(err));
}
);
return ret.promise;
}
And the code above changes to:
function NetworkError(reason) { this.reason = reason; }
function makeIndirectPromise(httpPromise) { /* see above */ }
eventResourcesCall = makeIndirectPromise($http.jsonp('https://apicall/to/serverA'));
eventsDetailsCall = makeIndirectPromise($http.get('https://apicall/to/serverB'));
$q.all([eventResourcesCall, eventsDetailsCall]).then(function(values){
var eventResources = values[0], eventsDetails = values[1];
if( eventResources instanceof NetworkError ) {
// handle error
}
else {
// eventResources is good, use it
}
// and so on...
});
From Angular doc to $q: as $http returns a promise, you can catch promise rejection using either:
$q.all([eventResourcesCall, eventsDetailsCall]).then(function(values){
//process data manipulation and merging on Success
}).catch(function(errors){
//Deal with your $http errors
}).finally(function(data){
});
or
$q.all([eventResourcesCall, eventsDetailsCall]).then(function(values){
//process data manipulation and merging on Success
}, function(errors){
//Deal with your $http errors
});

Resources