Combind both ideas from Bruce Martin and dan, I come up with this code.
It will return an assoc array.
usage:
    $data = fetch_assoc('select * from table1 where id=? and name=?','is',[1,'Sam']);
    $data = fetch_assoc('select * from table2');
    
result looks like this:
   $data = [
      'id' => 1,
      'name' => 'Sam',
      'age' => ...
      ...
      ...
   ];
Please forgive my poor coding ( and English)  XD
code:
<?php
    function fetch_assoc($sql, $types = false, $params = false) {
        
        $db = new MySQLi(HOST, USER, PASS, DB_NAME);
        $db->set_charset('utf8');
        
        $stmt = $db->prepare($sql);
        
        if (is_string($types) && is_array($params) && count($params) === strlen($types)) {
            $p = [];
            for($i = 0; $i<count($params); $i++){
                $p[$i] = &$params[$i];
            }
            call_user_func_array(array($stmt, 'bind_param'), array_merge(array($types), $p));
        }
        if (!$stmt->execute()) {
            return false;
        }
        $stmt->store_result();
        
        $metadata = $stmt->result_metadata();
        $fields = $metadata->fetch_fields();
        $results = [];
        $ref_results = [];
        foreach($fields as $field){
            $results[$field->name]=null;
            $ref_results[]=&$results[$field->name];
        }
        call_user_func_array(array($stmt, 'bind_result'), $ref_results);
        $data = [];
        while ($stmt->fetch()) {
            $data[] = $results;
        }
        $stmt->free_result();
        return  $data;
    }