Ember-Concurrency queue on task in component


#1

I have a problem with my current set-up, and i’m looking for some ideas or creative problem solving.

I have a model, with an attribute of type “file”, the “file” attribute is a custom type which stores an object with metadata of an uploaded file:

{ 
    "filename": "uploadedfile.jpg",
    "filesize": 121021010,
    "url": "http://mywebsite.com/file/uploadedfile.jpg"        
}

I have a custom component which handles the uploading of the file. You get a typical browse button, where you can browse your filesystem. After selecting a file, an ember-concurrency task is started, which uploads the file to an endpoint. After the task completes, the component returns the object (example above) which is then put in the attribute of the model. After which I can save the model, and file is then correctly attached to my model in my back-end.

In case the model doesn’t get saved, or the changes get cancelled. There is an automatic process deleting the uploaded files that didn’t get attached to any models. (this is a typical workflow in Drupal 8)

This is the situation. Now let me describe my problem.

In case someone is uploading a large file, it takes a while for the upload to complete. If someone clicks the save button that saves my model while the upload is still busy. I would like to queue the save behind the upload process. Because at the moment the save completes before the upload, and the file isn’t correctly linked to my model.

Now the problem is that my task that is uploading the file is encapsulated in a component. And the task saving the model is outside of the uploading component.

Is there a way of sharing the task or task instance, through a service or something, and how would I then queue my saving task behing the uploading task (possible multiple uploading tasks when there are multiple file fields on the model).


#2

first off because the task is in the context of the component it is dependent on the lifecycle of the component being in the DOM. Since you want these tasks to run to completion even when the user navigates away you may need the task to be in a service.

After more thought on this the task is actually in the scope of a single model. You might find it easier to reason about if the image saving is in the scope of the model. Models are global like services so the same semantics apply.

I think I might do this:

export default Model.extend({
  uploadImage: task(function* (image) {
    yield uploadImage(image);
    // don’t yield or return to prevent cancellations
    // from following down the task chain.
    this.get('debounceSave').perform();
  }).enqueue().maxConcurrency(5),

  debounceSave: task(function* () {
    yield timeout(700);
    yield this.save();
  }).restartable()
});

#3

Unfortunately this isnt entirely possible for my current situation, unless I completely overhaul the upload architecture.

Mainly because the component doing the upload doesnt have the model as a context, it just does the upload, and returns the metadata object.

I think the most elegant solution would be to pass in a ember-concurrency task group into my upload component. Is that possible? How?


#4

Then I think your next best option is to split your uploader component into a component and a service. That should be a localized refactor. The actual upload task would go on the service. And then the “save” action for the whole form would also trigger a task on the service. That way the two tasks could be aware of each other.