Here is one solution I can think of which uses just plain TeX.
\def\checkspace{\catcode`\ =12\relax\futurelet\csps\checkspaceA}
\def\checkspaceA{\catcode`\ =10\relax\if\space\csps\ \afterassignment\ignorespaces\expandafter\let\expandafter\csps\fi}
\def\foo{foo\checkspace}
test \foo bar
test foo bar
test \foo.
\bye
The idea is like so: \checkspace first makes spaces other so that they are not skipped and can be \futurelet'd.
Then we inspect the next token in \checkspaceA using \futurelet.
\checkspaceA makes spaces space tokens again, and if the token we are inspecting is a space, then a \ is inserted.
The purpose of the rest of the code inside the conditional in \checkspaceA is to make sure that if \checkspace is followed by multiple spaces, all the spaces following the first are ignored.
First I will explain the issue (to my understanding) so I can better explain my solution.
If we were to remove this code from \checkspaceA and do the following test:
\def\checkspaceA{\catcode`\ =10\relax\if\space\csps\ \fi}
test \foo bar
Then we would get test foo followed by a space, then an other space character (which looks like a slanted dash in cmr), then bar.
The issue is that once we do \futurelet\csps\checkspaceA in \checkspace, the token following \foo is tokenized into an other space token, and left in the stream, and this is what creates the weird dash character.
So we can alter the code slightly to get rid of this:
\def\checkspaceA{\catcode`\ =10\relax\if\space\csps\ \expandafter\let\expandafter\csps\fi}
test \foo bar
But now the issue is that since there are two spaces following \foo, the first one is correctly converted into \ by \checkspaceA.
But the next space token really followed an other token, and is therefore not ignored.
Doing \afterassignment\ignorespaces before the \let ensures that it is ignored.
This solution will not work for any character with catcode 10, and setting up the macros so that they work for other characters requires changing the catcodes of multiple characters and nesting \ifs.
In general I don't really see a reason to for these macros, I think it may just be simpler to follow your macros with an empty group.
You almost certainly won't run into weird unintended side effects doing so.
I'm not 100% sure that my macros will always work, if there is an issue with them, feel free to tell me and I'd be happy to attempt to fix them.