Although the question is already old, can help more developers. <br/>
You can create a modal operator and check the state of the 'is_dirty' property of the image.

    import bpy
    
    class ModalTimerOperator(bpy.types.Operator):
        bl_idname = "wm.modal_timer_operator"
        bl_label = "Modal Timer Operator"
        _timer = None
        _img = None
    
        def modal(self, context, event):
            if event.type in {'RIGHTMOUSE', 'ESC'}:
                self.cancel(context)
                return {'CANCELLED'}
    
            if event.type == 'TIMER':
                if self._img.is_dirty: # <--- Wait until the image is marked as dirty
                    self.finish(context)
                    return {'FINISHED'}
    
            return {'PASS_THROUGH'}
    
        def execute(self, context: bpy.context):
            if context.scene.render.engine != 'CYCLES':
                context.scene.render.engine = 'CYCLES'
            
            if not context.active_object.select_get() or not context.active_object.type == 'MESH':
                self.report({'WARNING'}, "No valid selected objects")
                return {'FINISHED'}
    
            obj = context.active_object
            mats = obj.data.materials
            mats_len = len(mats)
    
            if mats_len == 0 or not mats[0]:
                mat_new = bpy.data.materials.new('BakeMaterial')
                if mats_len == 0:
                    mats.append(mat_new)
                else:
                    mats[0] = mat_new
                pass
    
            mat = mats[0]
            mat.use_nodes = True
            nodes = mat.node_tree.nodes
            tex_node = nodes.new('ShaderNodeTexImage')
            tex_node.name = 'BakeNode'
            tex_node.select = True
            nodes.active = tex_node
            self._img = bpy.data.images.new('BakeResult', 1024, 1024)
            tex_node.image = self._img
    
            self.report({'INFO'}, "Execute")
            bpy.ops.object.bake('INVOKE_DEFAULT', type='DIFFUSE')
    
            wm = context.window_manager
            self._timer = wm.event_timer_add(0.5, window=context.window)
            wm.modal_handler_add(self)
            return {'RUNNING_MODAL'}
    
        def cancel(self, context):
            self.report({'INFO'}, "Baking map cancelled")
    
        def finish(self, context):
            wm = context.window_manager
            wm.event_timer_remove(self._timer)
            self.report({'INFO'}, "Baking map completed")
            #save_image() # <--- Call the function after baking
    
    def register():
        bpy.utils.register_class(ModalTimerOperator)
    
    def unregister():
        bpy.utils.unregister_class(ModalTimerOperator)
    
    if __name__ == "__main__":
        register()
        bpy.ops.wm.modal_timer_operator()