Immer: Idea: non-callback form of immer

Created on 25 Jan 2019  ·  18Comments  ·  Source: immerjs/immer

Occasionally I have cases where the callback based api of Immer is to limited, for example when I don't have explicit control over the flow or timing that drafts should be modified. For example in asynchronous processes where the draft should be updated bit by bit. In those cases I would like an api in which I can have more control, for example:

import { createDraft, finishDraft } from "immer"

const todo = { title: '', done: false }

async function loadTodo() {
    const draftTodo = createDraft(todo)
    draftTodo.title = await fetchTitle()
    draftTodo.done = await fetchDone()
    todo = finishDraft(draftTodo)
}
proposal released

Most helpful comment

Oh, that's a great idea! We might be able to support that and the OP in one fell swoop, since they both defer the finalize phase. If not, I would implement your idea first.

I'm working on a PR for this! 👍

All 18 comments

I don't know much about immer internals, but could a similar result be achieved by allowing the user to pass a function that returns a promise to immer? Something along the lines of:

import produce from 'immer'

const todo = { title: '', done: false }

function loadTodo() {
  return produce(todo, async draft => {
    draft.title = await fetchTitle()
    draft.done = await fetchDone()
  })
}

Just one idea off the back of this. Would it / might it be possible to leave the draft 'active' so you can produce multiple variants of it? For example:

const draft = createDraft(todo);
draft.title = 'Updated title';
const todoWithUpdatedTitle = finishDraft(draft);
draft.done = true;
const todoWithUpdatedTitleAndDone = finishDraft(draft);

I think this is a really good feature.

Yurickh's suggested API looks pretty straightforward and makes a lot of sense (when I first used Immer, I thought it would support that exact call).

It would meet our needs as we would like to be able to pass a draft into asynchronous workflow logic and then finalize the draft upon workflow completion.

Note that @Yurickh's proposal would be a breaking change.

edit: Nevermind, I misunderstood!

edit 2: Actually, this __will__ be a breaking change. See here: https://github.com/mweststrate/immer/pull/307#issuecomment-459968964

Would it, really? I mean, it looks like an extension of the current API, rather than breaking.

@Yurickh The current behavior returns the Promise as a replacement of the base state. We can't assume no one is relying on that.

I may have expressed myself poorly, but that was kind of how I was thinking it would go.
produce would return the promise that would resolve to the final value.
Usage would go like:

const newTodo = await produce(todo, async draft => { /* manipulation */ })

Oh, that's a great idea! We might be able to support that and the OP in one fell swoop, since they both defer the finalize phase. If not, I would implement your idea first.

I'm working on a PR for this! 👍

If produce returns a Promise then all functions that use it need to be async in order to await it (or handle promises), right? Maybe I'm misunderstanding.

I think the async example in the original post is only one of many possible use cases. The non-callback form that @mweststrate suggested is the most basic form that immer could take, and can easily be used to create produce and produceAsync variants.

I really like @Yurickh's suggestion, and I think it could be implemented without a breaking change:

// if recipe returns a promise, produce returns a promise too
const nextState = await produce(state, async (draft) => draft);

// if recipe does not return a promise, then produce 
// is synchronous and returns the value right away.
// (today's behaviour -> no breaking change)
const nextState = produce(state, draft => draft);

@pantoninho It's already implemented in #305 😉

That was fast!
Thank you, this will be very useful for me.

Great work @aleclarson and everyone involved.

Looks like async/await support __will__ be a breaking change after all. See here: https://github.com/mweststrate/immer/pull/307#issuecomment-459968964

:tada: This issue has been resolved in version 2.0.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

I'm curious about what happens when there are multiple async draft edits going at once. It seems like after one of them has gone async the state may be modified before it picks up again, which means it's now working against an older version of state. It also seems like there's some potential for conflicts due to the async sequencing.

Am I misunderstanding, or is there a pretty good source of potential danger here if you don't understand what's going on under the covers pretty well?

I'm not sure if I understand, state is immutable, so there is no "older"
one. Maybe demonstrate with some code what you mean?

Op zo 3 feb. 2019 03:11 schreef scriby notifications@github.com:

I'm curious about what happens when there are multiple async draft edits
going at once. It seems like after one of them has gone async the state may
be modified before it picks up again, which means it's now working against
an older version of state. It also seems like there's some potential for
conflicts due to the async sequencing.

Am I misunderstanding, or is there a pretty good source of potential
danger here if you don't understand what's going on under the covers pretty
well?


You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/mweststrate/immer/issues/302#issuecomment-460016466,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABvGhHRs4NcHlbWDzEhtuRiwExglxpqpks5vJkVdgaJpZM4aSX7u
.

I think you're right about the base library, I was thinking about this in the context of something I'm trying to do with immer. I'll open a new issue to discuss instead of derailing this one too much more. Thanks!

Was this page helpful?
0 / 5 - 0 ratings