I'm attempting to introduce a queue-type system to a JS class to allow for Async method chaining, ideally, I'd like to perform operations on the class instance using these async methods and return "this" instance.
export class Queue {
constructor() {
this.queue = Promise.resolve()
this.firstRequestStarted = false
this.firstRequestStatusCode = 0
this.secondRequestStarted = false
this.secondRequestStatusCode = 0
}
then(callback) {
callback(this.queue)
}
chain(callback) {
return this.queue = this.queue.then(callback)
}
first() {
this.chain(async () => {
try {
this.firstRequestStarted = true
const response = await axios.get("https://stackoverflow.com/questions")
this.firstRequestStatusCode = response.status
return this
}
catch (e) {
const { message = "" } = e || {}
return Promise.reject({ message })
}
})
return this
}
second() {
this.chain(async () => {
try {
this.secondRequestStarted = true
const response = await axios.get("https://stackoverflow.com/")
this.secondRequestStatusCode = response.status
return this
}
catch (e) {
const { message = "" } = e || {}
return Promise.reject({ message })
}
})
return this
}
}
Functions are added to the queue, and as we await them, the "then" method will handle their execution.
const x = await new Queue()
.first()
.second()
console.log(x)
The challenge I'm facing is that I can never actually get "this" (instance of Queue) back to x.
1) x === undefined
2) "Chaining cycle detected for promise #<Promise>"
or ( I haven't been able to track down where this one is coming from, node error)
3) finished with exit code 130 (interrupted by signal 2: SIGINT)
I have tried adding a "consume" method, which simply returns "this", this leads to error #2 above
me() {
this.chain( () => {
try {
return this
}
catch (e) {
const { message = "" } = e || {}
return Promise.reject({ message })
}
})
return this
}
The confusion on my part, is that if I use any value other than "this", it works as expected
me() {
this.chain( () => {
try {
return "test"
}
catch (e) {
const { message = "" } = e || {}
return Promise.reject({ message })
}
})
return this
}
x === "test"
I'm also able to return the values associated to this with something like the following
return {...this}
Ideally, I'd like to return the instance of Queue to X, as I plan on modifying the properties of the Queue instance through my async methods, await them, and be returned with an "initialized" instance of Queue.
Any input would be greatly appreciated - thank you!
The problem is that your Queue instances are thenable (have a .then() method), and the promise is tried to be resolved with itself (this.queue). See also here or there.
You have two options:
Do not resolve your promise with the instance, but write
const x = new Queue().first().second();
await x;
console.log(x);
remove the then method from your class, then call
const x = new Queue().first().second().queue;
console.log(x);
(or possibly introduce a getter method - .get(), .toPromise() - instead of directly accessing .queue)
Think of how Rails, e.g. allows you to define a property as associated with another:
class Customer < ActiveRecord::Base
has_many :orders
end
This does not set up a database column for orders. Instead, it creates a getter for orders, which allows us to do
#orders = #customer.orders
Which goes and gets the related orders objects.
In JS, we can easily do that with getters:
{
name: "John",
get orders() {
// get the order stuff here
}
}
But Rails is sync, and in JS, if in our example, as is reasonable, we are going to the database, we would be doing it async.
How would we create async getters (and setters, for that matter)?
Would we return a promise that eventually gets resolved?
{
name: "John",
get orders() {
// create a promise
// pseudo-code for db and promise...
db.find("orders",{customer:"John"},function(err,data) {
promise.resolve(data);
});
return promise;
}
}
which would allow us to do
customer.orders.then(....);
Or would we do it more angular-style, where we would automatically resolve it into a value?
To sum, how do we implement async getters?
The get and set function keywords seem to be incompatible with the async keyword. However, since async/await is just a wrapper around Promises, you can just use a Promise to make your functions "await-able".
Note: It should be possible to use the Object.defineProperty method to assign an async function to a setter or getter.
getter
Promises work well with getters.
Here, I'm using the Node.js 8 builtin util.promisify() function that converts a node style callback ("nodeback") to a Promise in a single line. This makes it very easy to write an await-able getter.
var util = require('util');
class Foo {
get orders() {
return util.promisify(db.find)("orders", {customer: this.name});
}
};
// We can't use await outside of an async function
(async function() {
var bar = new Foo();
bar.name = 'John'; // Since getters cannot take arguments
console.log(await bar.orders);
})();
setter
For setters, it gets a little weird.
You can of course pass a Promise to a setter as an argument and do whatever inside, whether you wait for the Promise to be fulfilled or not.
However, I imagine a more useful use-case (the one that brought me here!) would be to use to the setter and then awaiting that operation to be completed in whatever context the setter was used from. This unfortunately is not possible as the return value from the setter function is discarded.
function makePromise(delay, val) {
return new Promise(resolve => {
setTimeout(() => resolve(val), delay);
});
}
class SetTest {
set foo(p) {
return p.then(function(val) {
// Do something with val that takes time
return makePromise(2000, val);
}).then(console.log);
}
};
var bar = new SetTest();
var promisedValue = makePromise(1000, 'Foo');
(async function() {
await (bar.foo = promisedValue);
console.log('Done!');
})();
In this example, the Done! is printed to the console after 1 second and the Foo is printed 2 seconds after that. This is because the await is waiting for promisedValue to be fulfilled and it never sees the Promise used/generated inside the setter.
As for asynchronous getters, you may just do something like this:
const object = {};
Object.defineProperty(object, 'myProperty', {
async get() {
// Your awaited calls
return /* Your value */;
}
});
Rather, the problem arises when it comes to asynchronous setters.
Since the expression a = b always produce b, there is nothing one can do to avoid this, i.e. no setter in the object holding the property a can override this behavior.
Since I stumbled upon this problem as well, I could figure out asynchronous setters were literally impossible. So, I realized I had to choose an alternative design for use in place of async setters. And then I came up with the following alternative syntax:
console.log(await myObject.myProperty); // Get the value of the property asynchronously
await myObject.myProperty(newValue); // Set the value of the property asynchronously
I got it working with the following code,
function asyncProperty(descriptor) {
const newDescriptor = Object.assign({}, descriptor);
delete newDescriptor.set;
let promise;
function addListener(key) {
return callback => (promise || (promise = descriptor.get()))[key](callback);
}
newDescriptor.get = () => new Proxy(descriptor.set, {
has(target, key) {
return Reflect.has(target, key) || key === 'then' || key === 'catch';
},
get(target, key) {
if (key === 'then' || key === 'catch')
return addListener(key);
return Reflect.get(target, key);
}
});
return newDescriptor;
}
which returns a descriptor for an asynchronous property, given another descriptor that is allowed to define something that looks like an asynchronous setter.
You can use the above code as follows:
function time(millis) {
return new Promise(resolve => setTimeout(resolve, millis));
}
const object = Object.create({}, {
myProperty: asyncProperty({
async get() {
await time(1000);
return 'My value';
},
async set(value) {
await time(5000);
console.log('new value is', value);
}
})
});
Once you've set up with an asynchronous property like the above, you can set it as already illustrated:
(async function() {
console.log('getting...');
console.log('value from getter is', await object.myProperty);
console.log('setting...');
await object.myProperty('My new value');
console.log('done');
})();
The following allows for async setters in proxy handlers following the convention in Davide Cannizzo's answer.
var obj = new Proxy({}, asyncHandler({
async get (target, key, receiver) {
await new Promise(a => setTimeout(a, 1000))
return target[key]
},
async set (target, key, val, receiver) {
await new Promise(a => setTimeout(a, 1000))
return target[key] = val
}
}))
await obj.foo('bar') // set obj.foo = 'bar' asynchronously
console.log(await obj.foo) // 'bar'
function asyncHandler (h={}) {
const getter = h.get
const setter = h.set
let handler = Object.assign({}, h)
handler.set = () => false
handler.get = (...args) => {
let promise
return new Proxy(()=>{}, {
apply: (target, self, argv) => {
return setter(args[0], args[1], argv[0], args[2])
},
get: (target, key, receiver) => {
if (key == 'then' || key == 'catch') {
return callback => {
if (!promise) promise = getter(...args)
return promise[key](callback)
}
}
}
})
}
return handler
}
Here's another approach to this. It creates an extra wrapper, but in other aspects it covers what one would expect, including usage of await (this is TypeScript, just strip the : Promise<..> bit that sets return value type to get JS):
// this doesn't work
private get async authedClientPromise(): Promise<nanoClient.ServerScope> {
await this.makeSureAuthorized()
return this.client
}
// but this does
private get authedClientPromise(): Promise<nanoClient.ServerScope> {
return (async () => {
await this.makeSureAuthorized()
return this.client
})()
}
Here's how you could implement your get orders function
function get(name) {
return new Promise(function(resolve, reject) {
db.find("orders", {customer: name}, function(err, data) {
if (err) reject(err);
else resolve(data);
});
});
}
You could call this function like
customer.get("John").then(data => {
// Process data here...
}).catch(err => {
// Process error here...
});
I'm unittesting a es6 class and want my test to verify the values written to the class variables.
My class has the folowing method:
export default class ctrl{
constructor(){}
postClosingNote(payload) {
this._headerNoteService.createNewNote(payload).then(data => {
this.note = {};
this.claimNotes = data;
this.returnToClaimHeader();
});
}
}
Service method:
createNewNote(postData){
return this._http.post(`${api}`, postData).then(res => res.data);
}
Unittest:
beforeEach(() => {
when(headerNoteService.createNewNote).calledWith(newNote).mockReturnValue(Promise.resolve({'claimNotes': 'txt'}));
});
const newNote = {
text: "txt",
claimDescriptionTypeId: 4,
claimHeaderId: headerId
};
test('Confirm that newly submitted note is added to the headers notes', () => {
target = new ctrl();
target.postClosingNote(newNote);
expect(target.claimNotes).toEqual({'claimNotes': 'txt'});
});
Output from running test:
Expected value to equal:
{"claimNotes": "txt"}
Received:
undefined
logging target to console does not include any reference to this.note or this.claimNotes
I believe this is happening because postClosingNote() returns immediately before the promise is resolved. You need to wait for it to resolve before testing the result. You can return the promise from postClosingNote and wait it to resolve in your test.
(haven't actually run this code, there might be syntax errors but you get the idea):
postClosingNote(payload) {
return this._headerNoteService.createNewNote(payload).then(data => {
this.note = {};
this.claimNotes = data;
this.returnToClaimHeader();
});
}
Unittest:
test('Confirm that newly submitted note is added to the headers notes', () => {
target = new ctrl();
target.postClosingNote(newNote).then(result => {
expect(target.claimNotes).toEqual({'claimNotes': 'txt'});
}
});
Using async/await worked:
test('Confirm that newly submitted note is added to the headers notes', async () => {
target = new ctrl();
await target.postClosingNote(newNote);
expect(target.claimNotes).toEqual({'claimNotes': 'txt'});
});
I know this questions is asked several times in several ways. But I realy dont get it:
I have a promise with a resolved value. I can console.log this object and everything seems to be fine. I can see, what I want to see.
I use PouchDB and NuxtJS (VueJS)
import PouchDB from 'pouchdb'
let testdb = new PouchDB('testdb');
let testDoc = function () {
testdb.get('2').then(function (doc) {
console.log(doc);
}).catch(function (err) {
console.log(err);
});
}
This works great. My result is what I expect and hope to see:
{
text: "testen",
_id: "2",
_rev: "1-c7e7c73d264aa5e6ed6b5cc10df35c5a"
}
Perfect. But now I am struggeling with returning this value, so other functions can access to it. Especially returning this data. In VueJS eg like that:
// ..
export default {
data() {
return {
doc: testDoc
}
}
}
So I can access to it via instance. But ofcourse, If I do it like that, data is promise
data: [
doc: promise
]
But I need the value, not what it is. I dont understand how to return the value.
I have read several How To´s. I guess, I understand the different between Callback and Promise. With both and async functions I get the same result. But all example are always with console.log(). But this works for me.
Has anyone an example hot to access this (scoped or nested?) value?
If I return the data:
let testdb = new PouchDB('testdb');
let testDoc = function () {
testdb.get('2').then(function (doc) {
return doc;
}).catch(function (err) {
console.log(err);
});
}
Why hasnt testDoc the value now? Or where the hack is the value?
I always have done it via commiting the value into the vuex store. This also works great.
let fetchOrga = async function({ store }) {
try {
let orgaDoc = await orgadb.get('orga');
store.commit('orgaUpdate', orgaDoc)
} catch (err) {
console.log(err);
}
}
But like I said, I want to have this data directly under control via IndexedDB
You can use async/await to wait until promise resolve:
// nuxt.js way
async asyncData() {
let testdb = new PouchDB('testdb');
return {
doc: await testdb.get('2'),
};
},
UPD (by comments):
data() {
return {
isReady: false,
doc: null,
};
},
async mounted() {
let testdb = new PouchDB('testdb');
this.doc = await testdb.get('2');
this.isReady = true;
},
In the mount of the component you should update your state doc then your doc will be available to work with anywhere in your inside your component.
export default {
data() {
return {
doc: [],
error : ""
}
},
mounted: () => {
testdb.get('2').then(doc => {
this.doc = doc;
}).catch(function(err) {
this.error = err;
});
}
}
testDoc in your example now contains a function. Not a promise nor the doc you're getting from the promnise. To get the actual doc you need to get inside the promise. Like this
let testdb = new PouchDB('testdb');
testdb.get('2').then(function (doc) {
// Your doc is here
console.log(doc);
return doc;
}).catch(function (err) {
console.log(err);
});
Alternatively you can use the async/await syntax, like this
let doc = await testdb.get('2');
// Your doc is here
console.log(doc);
I am trying to save the name that a user enters into a TextInput such that every proceeding time a user opens the app their name will still be saved.
I am trying to use react-native's Asynchronous storage to get the name inside the componentDidMount() function. I believe that the value is not being set inside the onSubmit() method when it is being called. I tried removing the await keyword in the async function. I read other the documentation and can't find where I am going wrong.
onSubmit = () => {
var that = this
var data = this.state.textValue;
nameDict['name'] = data;
_setData = async () => {
try {
const value = await AsyncStorage.setItem('name',data)
that.setState({textValue:'',name:data})
} catch (error) {
that.setState({textValue:'',name:'error'})
}
}
}
componentDidMount(){
var that = this;
var name = ''
this.watchId = navigator.geolocation.watchPosition((position)=>{
_retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('name')
name = value
} catch (error) {
// Error retrieving data
}
}
}
The name variable in ComponentDidMount() is always an empty string, so either it has not been changed at all or the getItem() function is not resolving and returning the name. However, I am not sure which of the two it could be. Am I using the Async functions incorrectly?
you have to set set componentDidMount to async also
example using your data:
async componentDidMount(){
var that = this;
var name = ''
this.watchId = navigator.geolocation.watchPosition((position)=>{
_retrieveData = async () => {
try {
await AsyncStorage.getItem('name').then((value)=>name=value);
} catch (error) {
// Error retrieving data
}
}
}
I recommend making name global if you want to actually use the value.
next, this might be personal but i recommend to JSON.stringify() the values when storing data and JSON.parse() when retrieving the data
example when retrieving stringify data
async componentDidMount(){
var that = this;
var name = ''
this.watchId = navigator.geolocation.watchPosition((position)=>{
_retrieveData = async () => {
try {
await AsyncStorage.getItem('name').then((value)=>name=JSON.parse(value));
} catch (error) {
// Error retrieving data
}
}
}
if this was helpful please set as the answer if not please leave a comment.