diff --git a/CHANGELOG.md b/CHANGELOG.md
index 877bd26..97967c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,30 @@
 
 --------------------------------
 
+## v0.1.3
+
+> Since: 2024-07-02
+
+### New features
+
+- Add `callback` method on handles.
+  - Use `callback` method to directly obtain callback for such as `async`, `check` or `timer`.
+  - Use `listen_callback` method to obtain the callback for stream's `listen`.
+  - You can set up handling functions independently before performing any work.
+  - This can be very useful when operations may involve multiple starts and stops.
+- Add examples.
+
+## Bug fix
+
+- Fix the variable usage error in the `lib_t::open(string)` method.
+
+## Break changes
+
+- Change return value of `signal_t::start_oneshot`.
+  - `callback<int>` -> `promise<int>`
+
+--------------------------------
+
 ## v0.1.2
 
 > Date: 2024-07-01
@@ -25,7 +49,7 @@
 
 --------------------------------
 
-## v0.1.2
+## v0.1.1
 
 > Date: 2024-06-28
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0c568a1..b6d01ac 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,10 @@
 cmake_minimum_required(VERSION 3.9)
 project(libuvcxx)
 
+# -------------------------------------------------------------------------------
+# Options
+# -------------------------------------------------------------------------------
+
 # default using std=c++17 as libuvcxx has compatibility to C++11
 set(STD "17" CACHE STRING "Set CMAKE_CXX_STANDARD")
 
@@ -9,7 +13,10 @@ option(WERROR "If treat all warning as error" OFF)
 
 set(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/bin")
 
+# -------------------------------------------------------------------------------
 # Load libuv from submodule or system
+# -------------------------------------------------------------------------------
+
 if (EXISTS "${PROJECT_SOURCE_DIR}/libuv/CMakeLists.txt")
     # compile static libuv
     option(LIBUV_BUILD_SHARED "" OFF)
@@ -50,8 +57,9 @@ else ()
     message(STATUS "Using system libuv")
 endif ()
 
-include_directories("${PROJECT_SOURCE_DIR}/include")
-file(GLOB_RECURSE HEADERS "${PROJECT_SOURCE_DIR}/include/*.h")
+# -------------------------------------------------------------------------------
+# Message configuration items
+# -------------------------------------------------------------------------------
 
 if (STD)
     message(STATUS "Using --std=c++${STD}")
@@ -66,9 +74,54 @@ if (WERROR)
     message(STATUS "Enabled -Werror")
 endif ()
 
-# build examples
+# -------------------------------------------------------------------------------
+# Collect header files
+# -------------------------------------------------------------------------------
+
+include_directories("${PROJECT_SOURCE_DIR}/include")
+file(GLOB_RECURSE HEADERS "${PROJECT_SOURCE_DIR}/include/uvcxx/*.h")
+
+# -------------------------------------------------------------------------------
+# Generate single header
+# -------------------------------------------------------------------------------
+
+find_package(Python 3 COMPONENTS Interpreter)
+
+set(ENTRY_HEADER "${PROJECT_SOURCE_DIR}/include/uvcxx.h")
+set(SINGLE_HEADER "${PROJECT_SOURCE_DIR}/include/uvcxx-single.h")
+
+if (Python_FOUND)
+    if ("${Python_VERSION_MAJOR}" GREATER 3
+            OR ("${Python_VERSION_MAJOR}" EQUAL 3 AND "${Python_VERSION_MINOR}" GREATER_EQUAL 6))
+        message(STATUS "Generating the Single-Header")
+        # Use python3 generate single header
+        add_custom_command(OUTPUT "${SINGLE_HEADER}"
+                COMMAND ${Python_EXECUTABLE} "${PROJECT_SOURCE_DIR}/scripts/merge.py"
+                DEPENDS ${HEADERS}
+                COMMENT "python3 scripts/merge.py"
+                PRE_BUILD   # generate single header before any thing
+                VERBATIM
+        )
+        set_source_files_properties("${SINGLE_HEADER}" PROPERTIES GENERATED TRUE)
+        add_custom_target(single-header DEPENDS "${SINGLE_HEADER}")
+    else ()
+        message(WARNING "Requires Python 3.6 but only Python ${Python_VERSION} was found.")
+    endif ()
+else ()
+    message(WARNING "Since the Python is not found, the test case for Single-Header will not be generated.")
+endif ()
+
+list(APPEND HEADERS "${ENTRY_HEADER}")
+
+# -------------------------------------------------------------------------------
+# Build examples
+# -------------------------------------------------------------------------------
+
 add_subdirectory(examples)
 
-# add tests
+# -------------------------------------------------------------------------------
+# Add tests
+# -------------------------------------------------------------------------------
+
 enable_testing()
 add_subdirectory(tests)
diff --git a/README.md b/README.md
index c8466b0..e046631 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,8 @@ The `handle` can continue to work without any external references, so that expli
 
 See [lifecycle.md](docs/lifecycle.md) for more details.
 
+**Be careful** not to capture the handle itself by value in the lambda of the callback function, unless you are very clear about the lifecycle of the handle.
+
 ## 2. Compatibility
 
 `libuvcxx` requires at least `C++11` and is also compatible with the new features in `C++14` and `C++17`.
diff --git a/examples/async.cpp b/examples/async.cpp
index 50f91a6..76bf59c 100644
--- a/examples/async.cpp
+++ b/examples/async.cpp
@@ -3,8 +3,22 @@
 // L.eval: Let programmer get rid of only work jobs.
 //
 
+#include <iostream>
+
 #include "uvcxx/async.h"
 
 int main() {
-    return 0;
+    uv::loop_t loop;
+
+    uv::async_t async;
+    async.init(loop).call([]() {
+        std::cout << "async call" << std::endl;
+
+        throw uvcxx::close_handle();    //< close handle after once call
+    });
+
+    async.send();
+    std::cout << "async send" << std::endl;
+
+    return loop.run();
 }
diff --git a/examples/barrier.cpp b/examples/barrier.cpp
new file mode 100644
index 0000000..c13bbc3
--- /dev/null
+++ b/examples/barrier.cpp
@@ -0,0 +1,38 @@
+//
+// Created by Levalup.
+// L.eval: Let programmer get rid of only work jobs.
+//
+
+#include <iostream>
+#include <chrono>
+
+#include "uvcxx/barrier.h"
+#include "uvcxx/thread.h"
+#include "uvcxx/utilities.h"
+
+int main() {
+    uv::barrier_t barrier(2);
+    auto beg = std::chrono::steady_clock::now();
+
+    uv::thread_t thread1([&barrier, beg]() {
+        std::cout << uvcxx::catstr("thread-1 wait ", 0, "ms\n") << std::flush;
+        barrier.wait();
+        auto end = std::chrono::steady_clock::now();
+        auto spent = std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count();
+        std::cout << uvcxx::catstr("thread-1 exit after ", spent, "ms\n") << std::flush;
+    });
+
+    uv::thread_t thread2([&barrier, beg]() {
+        std::cout << uvcxx::catstr("thread-2 wait ", 100, "ms\n") << std::flush;
+        uv::sleep(100);
+        barrier.wait();
+        auto end = std::chrono::steady_clock::now();
+        auto spent = std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count();
+        std::cout << uvcxx::catstr("thread-2 exit after ", spent, "ms\n") << std::flush;
+    });
+
+    thread1.join();
+    thread2.join();
+
+    return 0;
+}
diff --git a/examples/fs.cpp b/examples/fs.cpp
index 7536798..4937398 100644
--- a/examples/fs.cpp
+++ b/examples/fs.cpp
@@ -4,33 +4,73 @@
 //
 
 #include "uvcxx/fs.h"
+#include "uvcxx/buf.h"
+#include "uvcxx/cxx/to_string.h"
 
 int main() {
-    std::cout << "----" << std::endl;
+    std::string content = "!!!!404!!!!";
 
-    uv::fs::open("a.txt", O_CREAT | O_WRONLY, 0777).then([&](int fd) {
+    // create a.txt and write content into it.
+    uv::fs::open("a.txt", UV_FS_O_CREAT | UV_FS_O_WRONLY, 0777).then([&](int fd) {
         std::cout << "open fd = " << fd << std::endl;
-        uv::fs::write(fd, "!!!!404!!!!", 0).then([fd](ssize_t) {
+        uv::fs::write(fd, content, 0).then([](ssize_t size) {
+            std::cout << "write size " << size << std::endl;
+        }).finally([fd]() {
+            // close file in finally, whether write succeed or not.
             (void) uv::fs::close(fd);
         });
-        // throw uvcxx::exception(UV_EAGAIN);
-        // throw std::logic_error("throw logic after close");
     }).except<uvcxx::errcode>([](const uvcxx::errcode &e) {
         std::cerr << "errcode = " << e.code() << std::endl;
-    }).except<std::exception>([](const std::exception &e) {
-        std::cerr << e.what() << std::endl;
+        exit(-1);
     });
 
+    uv::default_loop().run();
+
+    // read a.txt content
+    uv::fs::open("a.txt", UV_FS_O_RDONLY).then([&](int fd) {
+        uv::buf_t buf(1024);
+        uv::fs::read(fd, buf, 0).then([buf](ssize_t size) {
+            std::cout << "read size " << size << std::endl;
+            std::cout << "read content: " << std::string(buf.data(), size) << std::endl;
+        }).finally([fd]() {
+            // close file in finally, whether write succeed or not.
+            (void) uv::fs::close(fd);
+        });
+    });
+
+    uv::default_loop().run();
+
     {
+        // call sync version mkdir
         uv::fs_t tmp;
         uv::fs::mkdir(nullptr, tmp, "tmp", 0755, nullptr);
     }
 
-    uv::fs::mkdtemp(std::string("tmp") + "/tfolder.XXXXXX").then([](const char *path) {
+    // make temp folder
+    uv::fs::mkdtemp(std::string("tmp") + "/folder.XXXXXX").then([](const char *path) {
         std::cout << "mkdtemp " << path << std::endl;
         uv::fs_t tmp;
         uv::fs::rmdir(nullptr, tmp, path, nullptr);
     });
 
+    // readdir
+    uv::fs::opendir("tmp").then([](uv_dir_t *dir) {
+        uv::fs::readdir(dir, 1024).then([](uv_dirent_t *file, size_t size) {
+            for (decltype(size) i = 0; i < size; ++i) {
+                std::cout << "<" << uvcxx::to_string(file[i].type) << "> " << file[i].name << std::endl;
+            }
+        }).finally([dir]() {
+            (void) uv::fs::closedir(dir);
+        });
+    });
+
+    // scandir
+    uv::fs::scandir("tmp", 0).then([](const uv::fs::scan_next &next) {
+        uv_dirent_t file{};
+        while (next(&file) >= 0) {
+            std::cout << "<" << uvcxx::to_string(file.type) << "> " << file.name << std::endl;
+        }
+    });
+
     return uv::default_loop().run();
 }
diff --git a/examples/fs_event.cpp b/examples/fs_event.cpp
index 18b0390..9b0908a 100644
--- a/examples/fs_event.cpp
+++ b/examples/fs_event.cpp
@@ -15,19 +15,25 @@ int main() {
 
     { std::ofstream touch(target); }
 
+    // setup event watch callback
     uv::fs_event_t fs_event;
     fs_event.start(target, UV_FS_EVENT_RECURSIVE).call([](const char *filename, uv_fs_event events) {
         std::cout << filename << " " << uvcxx::to_string(events) << std::endl;
     });
+
+    // change file after 1 second
     uv::timer_t().start(1 * 1000, 1).call([&]() {
         std::ofstream touch(target);
         throw uvcxx::close_handle();
     });
+
+    // close fs_event
     uv::timer_t().start(2 * 1000, 1).call([&]() {
         fs_event.close(nullptr);
         throw uvcxx::close_handle();
     });
-    // 2 seconds to close
+
+    // wait about 2 seconds before run finished
     uv::default_loop().run();
     return 0;
 }
diff --git a/examples/getaddrinfo.cpp b/examples/getaddrinfo.cpp
index 894b7f5..a4e9880 100644
--- a/examples/getaddrinfo.cpp
+++ b/examples/getaddrinfo.cpp
@@ -5,6 +5,7 @@
 
 #include "uvcxx/getaddrinfo.h"
 #include "uvcxx/getnameinfo.h"
+#include "uvcxx/utilities.h"
 
 int main() {
     addrinfo hints = {};
@@ -16,10 +17,8 @@ int main() {
     uv::getaddrinfo(
             "baidu.com", nullptr, &hints
     ).then([](addrinfo *ai) {
-        char addr[INET6_ADDRSTRLEN];
         for (addrinfo *info = ai; info != nullptr; info = info->ai_next) {
-            uv_ip4_name((struct sockaddr_in *) info->ai_addr, addr, sizeof(addr));
-            std::cout << "Found address: " << addr << std::endl;
+            std::cout << "Found address: " << uv::ip4_name((struct sockaddr_in *) info->ai_addr) << std::endl;
             uv::getnameinfo(info->ai_addr, 0).then([](const char *hostname, const char *service) {
                 std::cout << "Name: " << hostname << " " << service << std::endl;
             });
diff --git a/examples/signal.cpp b/examples/signal.cpp
new file mode 100644
index 0000000..4e14a7e
--- /dev/null
+++ b/examples/signal.cpp
@@ -0,0 +1,73 @@
+//
+// Created by Levalup.
+// L.eval: Let programmer get rid of only work jobs.
+//
+
+#include <iostream>
+
+#include "uvcxx/signal.h"
+#include "uvcxx/utils/assert.h"
+
+int loop_handle_count(uv::loop_t &loop) {
+    int count = 0;
+    loop.walk([](uv_handle_t *, void *arg) {
+        (*(int *) arg)++;
+    }, &count);
+    return count;
+}
+
+int main(int argc, const char *argv[]) {
+    int sig = SIGINT;
+    const char *key = "Ctrl+C";
+
+    if (argc > 1 && argv[1] == std::string("ide")) {
+        sig = SIGTERM;
+        key = "[Stop]";
+    }
+
+    std::cout << "Test signal " << sig << " for " << key << std::endl;
+
+    uv::loop_t loop;
+
+    std::cout << "Press " << key << " at least " << 4 << " times." << std::endl;
+
+    int count = 0;
+    // hold handle to start and stop.
+    {
+        uv::signal_t signal(loop);
+        signal.start(sig).call([&, key](int) mutable {
+            ++count;
+            std::cout << "Received " << key << " " << count << std::endl;
+            signal.stop();
+        });
+        loop.run();
+        (void) signal.start(sig);
+        loop.run();
+    }
+    uvcxx_assert(loop_handle_count(loop) == 1, "count = ", loop_handle_count(loop));
+    loop.run();
+    uvcxx_assert(loop_handle_count(loop) == 0, "count = ", loop_handle_count(loop));
+    // start backend to watch signal
+    {
+        // start backend
+        uv::signal_t signal(loop);
+        signal.start(sig).call([key, &count](int) mutable {
+            ++count;
+            std::cout << "Received " << key << " " << count << std::endl;
+            throw uvcxx::close_handle();
+        });
+    }
+    loop.run();
+    uvcxx_assert(loop_handle_count(loop) == 0, "count = ", loop_handle_count(loop));
+    // start oneshot
+    {
+        uv::signal_t(loop).start_oneshot(sig).then([key, &count](int) mutable {
+            ++count;
+            std::cout << "Received " << key << " " << count << std::endl;
+        });
+    }
+    loop.run();
+    uvcxx_assert(loop_handle_count(loop) == 0);
+
+    return 0;
+}
diff --git a/include/uvcxx/async.h b/include/uvcxx/async.h
index a57dcd5..5094a1a 100644
--- a/include/uvcxx/async.h
+++ b/include/uvcxx/async.h
@@ -20,15 +20,20 @@ namespace uv {
         }
 
         UVCXX_NODISCARD
-        uvcxx::callback<> init() {
-            return init(default_loop());
+        uvcxx::callback<> callback() {
+            return get_data<data_t>()->send_cb.callback();;
         }
 
         UVCXX_NODISCARD
         uvcxx::callback<> init(const loop_t &loop) {
             UVCXX_APPLY(uv_async_init(loop, *this, raw_callback), nullptr);
             _detach_();
-            return get_data<data_t>()->send_cb.callback();
+            return callback();
+        }
+
+        UVCXX_NODISCARD
+        uvcxx::callback<> init() {
+            return init(default_loop());
         }
 
         int send() {
diff --git a/include/uvcxx/check.h b/include/uvcxx/check.h
index 828f198..f7293ca 100644
--- a/include/uvcxx/check.h
+++ b/include/uvcxx/check.h
@@ -22,11 +22,16 @@ namespace uv {
             _attach_close_();
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<> callback() {
+            return get_data<data_t>()->start_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<> start() {
             UVCXX_APPLY(uv_check_start(*this, raw_callback), nullptr);
             _detach_();
-            return get_data<data_t>()->start_cb.callback();
+            return callback();
         }
 
         void stop() {
diff --git a/include/uvcxx/fs_event.h b/include/uvcxx/fs_event.h
index 4d55b67..4b43028 100644
--- a/include/uvcxx/fs_event.h
+++ b/include/uvcxx/fs_event.h
@@ -28,11 +28,16 @@ namespace uv {
             _attach_close_();
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<const char *, uv_fs_event> callback() {
+            return get_data<data_t>()->start_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<const char *, uv_fs_event> start(uvcxx::string path, int flags) {
             UVCXX_APPLY(uv_fs_event_start(*this, raw_callback, path, flags), nullptr);
             _detach_();
-            return get_data<data_t>()->start_cb.callback();
+            return callback();
         }
 
         void stop() {
diff --git a/include/uvcxx/fs_poll.h b/include/uvcxx/fs_poll.h
index e92080c..6825f08 100644
--- a/include/uvcxx/fs_poll.h
+++ b/include/uvcxx/fs_poll.h
@@ -25,11 +25,16 @@ namespace uv {
             _attach_close_();
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<const uv_stat_t *, const uv_stat_t *> callback() {
+            return get_data<data_t>()->start_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<const uv_stat_t *, const uv_stat_t *> start(uvcxx::string path, unsigned int interval) {
             UVCXX_APPLY(uv_fs_poll_start(*this, raw_callback, path, interval), nullptr);
             _detach_();
-            return get_data<data_t>()->start_cb.callback();
+            return callback();
         }
 
         void stop() {
diff --git a/include/uvcxx/handle.h b/include/uvcxx/handle.h
index b739c12..d2a4e42 100644
--- a/include/uvcxx/handle.h
+++ b/include/uvcxx/handle.h
@@ -105,6 +105,11 @@ namespace uv {
 
 #endif
 
+        UVCXX_NODISCARD
+        uvcxx::promise<> close_callback() {
+            return get_data<data_t>()->close_cb.promise();
+        }
+
         void close(std::nullptr_t) {
             (void) close_for([&](void (*cb)(raw_t *)) {
                 uv_close(*this, cb);
diff --git a/include/uvcxx/idle.h b/include/uvcxx/idle.h
index c4a11aa..99bdbf1 100644
--- a/include/uvcxx/idle.h
+++ b/include/uvcxx/idle.h
@@ -22,11 +22,16 @@ namespace uv {
             _attach_close_();
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<> callback() {
+            return get_data<data_t>()->start_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<> start() {
             UVCXX_APPLY(uv_idle_start(*this, raw_callback), nullptr);
             _detach_();
-            return get_data<data_t>()->start_cb.callback();
+            return callback();
         }
 
         void stop() {
diff --git a/include/uvcxx/lib.h b/include/uvcxx/lib.h
index be47ff7..dba789a 100644
--- a/include/uvcxx/lib.h
+++ b/include/uvcxx/lib.h
@@ -15,6 +15,7 @@
 #include "cxx/string.h"
 #include "cxx/wrapper.h"
 #include "inner/base.h"
+#include "utils/defer.h"
 
 namespace uv {
     /**
@@ -47,7 +48,7 @@ namespace uv {
             auto lib = new uv_lib_t;
             uvcxx::defer_delete<uv_lib_t> delete_lib(lib);
 
-            UVCXX_APPLY(uv_dlopen(filename, *this), status, "can not open `", filename, "`");
+            UVCXX_APPLY(uv_dlopen(filename, lib), status, "can not open `", filename, "`");
 
             delete_lib.release();
             (void) reset_raw(lib, [](uv_lib_t *pre) {
diff --git a/include/uvcxx/poll.h b/include/uvcxx/poll.h
index d128b95..3da3264 100644
--- a/include/uvcxx/poll.h
+++ b/include/uvcxx/poll.h
@@ -45,6 +45,11 @@ namespace uv {
             return init_socket(default_loop(), socket);
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<int> callback() {
+            return get_data<data_t>()->start_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<int> start(int events) {
             auto data = get_data<data_t>();
diff --git a/include/uvcxx/prepare.h b/include/uvcxx/prepare.h
index cf8a0dd..3a8b9be 100644
--- a/include/uvcxx/prepare.h
+++ b/include/uvcxx/prepare.h
@@ -22,11 +22,16 @@ namespace uv {
             _attach_close_();
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<> callback() {
+            return get_data<data_t>()->start_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<> start() {
             UVCXX_APPLY(uv_prepare_start(*this, raw_callback), nullptr);
             _detach_();
-            return get_data<data_t>()->start_cb.callback();
+            return callback();
         }
 
         void stop() {
diff --git a/include/uvcxx/process.h b/include/uvcxx/process.h
index be8422f..86b1d31 100644
--- a/include/uvcxx/process.h
+++ b/include/uvcxx/process.h
@@ -242,6 +242,7 @@ namespace uv {
             return raw<raw_t>()->pid;
         }
 
+        UVCXX_NODISCARD
         uvcxx::promise<int64_t, int> spawn(const loop_t &loop, const uv_process_options_t *options) {
             auto fix_options = *options;
             fix_options.exit_cb = raw_exit_callback;
@@ -252,6 +253,7 @@ namespace uv {
             return get_data<data_t>()->exit_cb.promise();
         }
 
+        UVCXX_NODISCARD
         uvcxx::promise<int64_t, int> spawn(const uv_process_options_t *options) {
             return this->spawn(default_loop(), options);
         }
diff --git a/include/uvcxx/signal.h b/include/uvcxx/signal.h
index 4e63346..37a94c7 100644
--- a/include/uvcxx/signal.h
+++ b/include/uvcxx/signal.h
@@ -27,26 +27,30 @@ namespace uv {
             return raw<raw_t>()->signum;
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<int> callback() {
+            return get_data<data_t>()->start_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<int> start(int signum) {
             UVCXX_APPLY(uv_signal_start(*this, raw_callback, signum), nullptr);
             _detach_();
-            return get_data<data_t>()->start_cb.callback();
+            return callback();
         }
 
 #if UVCXX_SATISFY_VERSION(1, 12, 0)
 
         UVCXX_NODISCARD
-        uvcxx::callback<int> start_oneshot(int signum) {
-            UVCXX_APPLY(uv_signal_start_oneshot(*this, raw_callback, signum), nullptr);
+        uvcxx::promise<int> start_oneshot(int signum) {
+            UVCXX_APPLY(uv_signal_start_oneshot(*this, raw_oneshot_callback, signum), nullptr);
 
             _detach_();
 
             auto attachment = *this;
-            return get_data<data_t>()->start_cb.callback().finally(nullptr)
-                    .finally([attachment]() mutable {
-                        finally_recycle_oneshot(attachment);
-                    });
+            return get_data<data_t>()->start_oneshot_cb.promise().finally([attachment]() mutable {
+                finally_recycle_oneshot(attachment);
+            });
         }
 
 #endif
@@ -82,10 +86,17 @@ namespace uv {
             auto data = (data_t *) (handle->data);
             data->start_cb.emit(signum);
         }
+        static void raw_oneshot_callback(raw_t *handle, int signum) {
+            auto data = (data_t *) (handle->data);
+            data->start_oneshot_cb.resolve(signum);
+            data->start_oneshot_cb.finalize();
+            data->start_oneshot_cb.promise().then(nullptr).except(nullptr).finally(nullptr);
+        }
 
         class data_t : supper::data_t {
         public:
             uvcxx::callback_emitter<int> start_cb;
+            uvcxx::promise_emitter<int> start_oneshot_cb;
 
             explicit data_t(signal_t &handle)
                     : supper::data_t(handle) {
diff --git a/include/uvcxx/stream.h b/include/uvcxx/stream.h
index d599f33..54f92ef 100644
--- a/include/uvcxx/stream.h
+++ b/include/uvcxx/stream.h
@@ -34,6 +34,11 @@ namespace uv {
             return ::uv::shutdown(*this);
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<> listen_callback() {
+            return get_data<data_t>()->listen_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<> listen(int backlog) {
             auto data = get_data<data_t>();
@@ -52,13 +57,29 @@ namespace uv {
             UVCXX_PROXY(uv_accept(*this, client));
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<size_t, uv_buf_t *> alloc_callback() {
+            return get_data<data_t>()->alloc_cb.callback();
+        }
+
+        /**
+         * Different from `alloc_callback` which only acquires the callback function,
+         *     this method will clear the previously registered callback to avoid repeatedly `allocate`.
+         * @return
+         */
         UVCXX_NODISCARD
         uvcxx::callback<size_t, uv_buf_t *> alloc() {
             // this alloc is not under Running state, so no `_detach_` applied.
             // memory alloc can not register multi-times callback
+            // use call(nullptr) to avoid call alloc multi times.
             return get_data<data_t>()->alloc_cb.callback().call(nullptr);
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<ssize_t, const uv_buf_t *> read_callback() {
+            return get_data<data_t>()->read_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<ssize_t, const uv_buf_t *> read_start() {
             auto data = get_data<data_t>();
@@ -282,9 +303,15 @@ namespace uv {
 
         virtual stream_t accept() = 0;
 
+        UVCXX_NODISCARD
+        uvcxx::callback<stream_t> accept_callback() {
+            return data<data_t>()->accept_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<stream_t> listen_accept(int backlog) {
             auto data = this->data<data_t>();
+
             this->listen(backlog).call(nullptr).call([data, this]() {
                 data->accept_cb.emit(this->accept());
             }).except(nullptr).except([data](const std::exception_ptr &p) -> bool {
diff --git a/include/uvcxx/timer.h b/include/uvcxx/timer.h
index 7e9ad58..3bceec9 100644
--- a/include/uvcxx/timer.h
+++ b/include/uvcxx/timer.h
@@ -22,11 +22,16 @@ namespace uv {
             _attach_close_();
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<> callback() {
+            return get_data<data_t>()->start_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<> start(uint64_t timeout, uint64_t repeat) {
             UVCXX_APPLY(uv_timer_start(*this, raw_callback, timeout, repeat), nullptr);
             _detach_();
-            return get_data<data_t>()->start_cb.callback();
+            return callback();
         }
 
         int stop() {
diff --git a/include/uvcxx/udp.h b/include/uvcxx/udp.h
index 2d9a125..9ffea70 100644
--- a/include/uvcxx/udp.h
+++ b/include/uvcxx/udp.h
@@ -171,19 +171,35 @@ namespace uv {
             return try_send(buffers.data(), (unsigned int) buffers.size(), addr);
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<size_t, uv_buf_t *> alloc_callback() {
+            return get_data<data_t>()->alloc_cb.callback();
+        }
+
+        /**
+         * Different from `alloc_callback` which only acquires the callback function,
+         *     this method will clear the previously registered callback to avoid repeatedly `allocate`.
+         * @return
+         */
         UVCXX_NODISCARD
         uvcxx::callback<size_t, uv_buf_t *> alloc() {
             // this alloc is not under Running state, so no `_detach_` applied.
             // memory alloc can not register multi-times callback
+            // use call(nullptr) to avoid call alloc multi times.
             return get_data<data_t>()->alloc_cb.callback().call(nullptr);
         }
 
+        UVCXX_NODISCARD
+        uvcxx::callback<ssize_t, const uv_buf_t *, const sockaddr *, unsigned> recv_callback() {
+            return get_data<data_t>()->recv_cb.callback();
+        }
+
         UVCXX_NODISCARD
         uvcxx::callback<ssize_t, const uv_buf_t *, const sockaddr *, unsigned>
         recv_start() {
             UVCXX_APPLY(uv_udp_recv_start(*this, raw_alloc_callback, raw_recv_callback), nullptr);
             _detach_();
-            return get_data<data_t>()->recv_cb.callback();
+            return recv_callback();
         }
 
 #if UVCXX_SATISFY_VERSION(1, 39, 0)
diff --git a/include/uvcxx/utilities.h b/include/uvcxx/utilities.h
index fa84983..b6243e2 100644
--- a/include/uvcxx/utilities.h
+++ b/include/uvcxx/utilities.h
@@ -221,16 +221,34 @@ namespace uv {
         UVCXX_PROXY(uv_ip4_name(src, dst, size));
     }
 
+    inline std::string ip4_name(const sockaddr_in *src) {
+        char dst[UV_IF_NAMESIZE] = {0};
+        (void) ip4_name(src, dst, sizeof(dst));
+        return dst;
+    }
+
     inline int ip6_name(const sockaddr_in6 *src, char *dst, size_t size) {
         UVCXX_PROXY(uv_ip6_name(src, dst, size));
     }
 
+    inline std::string ip6_name(const sockaddr_in6 *src) {
+        char dst[UV_IF_NAMESIZE] = {0};
+        (void) ip6_name(src, dst, sizeof(dst));
+        return dst;
+    }
+
 #if UVCXX_SATISFY_VERSION(1, 43, 0)
 
     inline int ip_name(const sockaddr *src, char *dst, size_t size) {
         UVCXX_PROXY(uv_ip_name(src, dst, size));
     }
 
+    inline std::string ip_name(const sockaddr *src) {
+        char dst[UV_IF_NAMESIZE] = {0};
+        (void) ip_name(src, dst, sizeof(dst));
+        return dst;
+    }
+
 #endif
 
     inline int inet_ntop(int af, uvcxx::string src, char *dst, size_t size) {
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e07f0b2..a783f48 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -45,6 +45,11 @@ foreach (path ${FILES})
         endif ()
         string(REGEX MATCH "^[^.]*" file "${file_ext}")
 
+        # skip single header if there is no that target
+        if (NOT TARGET single-header AND "${PREFIX}-${file}" STREQUAL "test-single-header")
+            continue()
+        endif ()
+
         # check if target exists
         list(FIND TARGETS "${PREFIX}-${file}" TARGET_INDEX)
         if (TARGET_INDEX GREATER -1)
@@ -59,6 +64,11 @@ foreach (path ${FILES})
     endif ()
 endforeach ()
 
+# explicitly add test-single-header target dependencies
+if (TARGET single-header)
+    add_dependencies(test-single-header single-header)
+endif ()
+
 if (WALL)
     if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
         foreach (target ${TARGETS})
diff --git a/tests/single-header.cpp b/tests/single-header.cpp
new file mode 100644
index 0000000..13ecc4c
--- /dev/null
+++ b/tests/single-header.cpp
@@ -0,0 +1,13 @@
+//
+// Created by Levalup.
+// L.eval: Let programmer get rid of only work jobs.
+//
+
+#include <iostream>
+
+#include "uvcxx-single.h"
+
+int main() {
+    std::cout << "Generation succeeded." << std::endl;
+    return 0;
+}