Using apiFetch for retrieving post data in Gutenberg

I’m getting a Promise with the post object inside when I do a
console.log( mypost )

Yes, because apiFetch() indeed returns a Promise object, and apiFetch() doesn’t assign the response received from the server to the mypost constant, and the return value of your then() callback is also not going to be assigned to the mypost variable.

How can I get the post object instead?

As suggested by React, you would want to add mypost as a local state in your component, then make your XHR/AJAX request (using apiFetch(), the native window.fetch(), Axios or whatever) in the componentDidMount() method of a class component, or use the useEffect hook in a function component.

Example using apiFetch() with useEffect:

import { useState, useEffect } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';

function MyComponent( { post_id } ) {
    const [ error, setError ]       = useState( null );
    const [ mypost, setPost ]       = useState( null );
    const [ isLoaded, setIsLoaded ] = useState( false );

    useEffect( () => {
        apiFetch( { path: `custombase/v1/post/${ post_id }` } ).then(
            ( result ) => {
                setIsLoaded( true );
                setPost( result );
            },
            ( error ) => {
                setIsLoaded( true );
                setError( error );
            }
        );
    }, [ post_id ] );

    if ( error ) {
        return <p>ERROR: { error.message }</p>;
    } else if ( ! isLoaded ) {
        return <p>Loading post { post_id }..</p>;
    } else if ( mypost && mypost.id ) {
        return <h3>Post <i>{ mypost.title || '#' + mypost.id }</i> loaded!</h3>;
    }
    return <p>No such post</p>;
}
// Sample usage: <MyComponent post_id="1239" />

Alternate Solution: Add an entity for your custom endpoint into the list of entities in the block editor.

  1. Add the entity using addEntities():

    import { dispatch } from '@wordpress/data';
    
    dispatch( 'core' ).addEntities( [{
        baseURL: '/custombase/v1/post',
        // The 'post' is not a post type - it's the "post" as in /post above. Also, "kind"
        // and "name" are not documented, so let's assume they form the above baseURL..
        kind: 'custombase/v1',
        name: 'post',
        label: 'Post or whatever',
    }] );
    
    // You can, for example, call the above (i.e. add the entity) before you register your
    // block type.
    
  2. Then use getEntityRecord() to fetch post data from the endpoint:

    const mypost = select( 'core' ).getEntityRecord( 'custombase/v1', 'post', 1239 );
    // Note that getEntityRecord() caches the results.
    
  3. And note that for getEntityRecord() to work correctly, your endpoint callback must use the lowercase id and not ID:

    // In the get_post_rest() function:
    
    $data = array(
        'id'    => $post->ID, // use id and *not* ID
        'title' => $post->post_title,
    );
    

So for example using useSelect, the above component (MyComponent) could now look like so:

import { useSelect } from '@wordpress/data';

function MyComponent( { post_id } ) {
    const { mypost, isLoading } = useSelect( ( select ) => {
        const args = [ 'custombase/v1', 'post', post_id ];

        return {
            mypost: select( 'core' ).getEntityRecord( ...args ),
            isLoading: select( 'core/data' ).isResolving( 'core', 'getEntityRecord', args )
        };
    }, [ post_id ] );

    if ( isLoading ) {
        return <p>Loading post { post_id }..</p>;
    } else if ( mypost && mypost.id ) {
        return <h3>Post <i>{ mypost.title || '#' + mypost.id }</i> loaded!</h3>;
    }
    return <p>No such post</p>;
}