diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index ce51a96411..17a438afe4 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -36,15 +36,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 namespace {
 
 constexpr auto kReloadChannelMembersTimeout = 1000; // 1 second wait before reload members in channel after adding
+constexpr auto kSaveCloudDraftTimeout = 1000; // save draft to the cloud with 1 sec extra delay
 constexpr auto kSmallDelayMs = 5;
 
 } // namespace
 
-ApiWrap::ApiWrap() : _messageDataResolveDelayed([this] { resolveMessageDatas(); }) {
+ApiWrap::ApiWrap()
+: _messageDataResolveDelayed([this] { resolveMessageDatas(); })
+, _webPagesTimer([this] { resolveWebPages(); })
+, _draftsSaveTimer([this] { saveDraftsToCloud(); }) {
 	Window::Theme::Background()->start();
-
-	connect(&_webPagesTimer, &QTimer::timeout, [this] { resolveWebPages(); });
-	connect(&_draftsSaveTimer, &QTimer::timeout, [this] { saveDraftsToCloud(); });
 }
 
 void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback) {
@@ -948,7 +949,7 @@ void ApiWrap::requestNotifySetting(PeerData *peer) {
 void ApiWrap::saveDraftToCloudDelayed(History *history) {
 	_draftsSaveRequestIds.insert(history, 0);
 	if (!_draftsSaveTimer.isActive()) {
-		_draftsSaveTimer.start(SaveCloudDraftTimeout);
+		_draftsSaveTimer.callOnce(kSaveCloudDraftTimeout);
 	}
 }
 
@@ -1263,20 +1264,22 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
 void ApiWrap::requestWebPageDelayed(WebPageData *page) {
 	if (page->pendingTill <= 0) return;
 	_webPagesPending.insert(page, 0);
-	int32 left = (page->pendingTill - unixtime()) * 1000;
+	auto left = (page->pendingTill - unixtime()) * 1000;
 	if (!_webPagesTimer.isActive() || left <= _webPagesTimer.remainingTime()) {
-		_webPagesTimer.start((left < 0 ? 0 : left) + 1);
+		_webPagesTimer.callOnce((left < 0 ? 0 : left) + 1);
 	}
 }
 
 void ApiWrap::clearWebPageRequest(WebPageData *page) {
 	_webPagesPending.remove(page);
-	if (_webPagesPending.isEmpty() && _webPagesTimer.isActive()) _webPagesTimer.stop();
+	if (_webPagesPending.isEmpty() && _webPagesTimer.isActive()) {
+		_webPagesTimer.cancel();
+	}
 }
 
 void ApiWrap::clearWebPageRequests() {
 	_webPagesPending.clear();
-	_webPagesTimer.stop();
+	_webPagesTimer.cancel();
 }
 
 void ApiWrap::resolveWebPages() {
@@ -1342,11 +1345,13 @@ void ApiWrap::resolveWebPages() {
 		}
 	}
 
-	if (m < INT_MAX) _webPagesTimer.start(m * 1000);
+	if (m < INT_MAX) {
+		_webPagesTimer.callOnce(m * 1000);
+	}
 }
 
 void ApiWrap::requestParticipantsCountDelayed(ChannelData *channel) {
-	QTimer::singleShot(kReloadChannelMembersTimeout, this, [this, channel] {
+	_participantsCountRequestTimer.call(kReloadChannelMembersTimeout, [this, channel] {
 		channel->updateFull(true);
 	});
 }
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 89fb325817..3b64d9a84a 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 */
 #pragma once
 
+#include "base/timer.h"
 #include "core/single_timer.h"
 #include "mtproto/sender.h"
 
@@ -35,7 +36,7 @@ inline const MTPVector<MTPChat> *getChatsFromMessagesChats(const MTPmessages_Cha
 
 } // namespace Api
 
-class ApiWrap : public QObject, private MTP::Sender {
+class ApiWrap : private MTP::Sender {
 public:
 	ApiWrap();
 
@@ -124,6 +125,7 @@ private:
 
 	PeerRequests _participantsRequests;
 	PeerRequests _botsRequests;
+	base::DelayedCallTimer _participantsCountRequestTimer;
 
 	typedef QPair<PeerData*, UserData*> KickRequest;
 	typedef QMap<KickRequest, mtpRequestId> KickRequests;
@@ -132,7 +134,7 @@ private:
 	QMap<ChannelData*, mtpRequestId> _selfParticipantRequests;
 
 	QMap<WebPageData*, mtpRequestId> _webPagesPending;
-	SingleTimer _webPagesTimer;
+	base::Timer _webPagesTimer;
 
 	QMap<uint64, QPair<uint64, mtpRequestId> > _stickerSetRequests;
 
@@ -143,7 +145,7 @@ private:
 	QMap<PeerData*, mtpRequestId> _notifySettingRequests;
 
 	QMap<History*, mtpRequestId> _draftsSaveRequestIds;
-	SingleTimer _draftsSaveTimer;
+	base::Timer _draftsSaveTimer;
 
 	OrderedSet<mtpRequestId> _stickerSetDisenableRequests;
 	Stickers::Order _stickersOrder;
diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp
index aeb2d98384..1ae87f5849 100644
--- a/Telegram/SourceFiles/application.cpp
+++ b/Telegram/SourceFiles/application.cpp
@@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "autoupdater.h"
 #include "window/notifications_manager.h"
 #include "messenger.h"
+#include "base/timer.h"
 
 namespace {
 
@@ -546,6 +547,7 @@ void adjustSingleTimers() {
 	if (auto a = application()) {
 		a->adjustSingleTimers();
 	}
+	base::Timer::Adjust();
 }
 
 #ifndef TDESKTOP_DISABLE_AUTOUPDATE
diff --git a/Telegram/SourceFiles/base/timer.cpp b/Telegram/SourceFiles/base/timer.cpp
new file mode 100644
index 0000000000..a2fe6d764a
--- /dev/null
+++ b/Telegram/SourceFiles/base/timer.cpp
@@ -0,0 +1,141 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
+*/
+#include "base/timer.h"
+
+namespace base {
+namespace {
+
+QObject *TimersAdjuster() {
+	static QObject adjuster;
+	return &adjuster;
+}
+
+} // namespace
+
+Timer::Timer(base::lambda<void()> callback) : QObject(nullptr)
+, _callback(std::move(callback))
+, _type(Qt::PreciseTimer)
+, _adjusted(false)
+, _repeat(Repeat::Interval) {
+	connect(TimersAdjuster(), &QObject::destroyed, this, [this] { adjust(); }, Qt::QueuedConnection);
+}
+
+void Timer::start(TimeMs timeout, Qt::TimerType type, Repeat repeat) {
+	cancel();
+
+	_type = type;
+	_repeat = repeat;
+	_adjusted = false;
+	setTimeout(timeout);
+	_timerId = startTimer(_timeout, _type);
+	if (_timerId) {
+		_next = getms(true) + _timeout;
+	} else {
+		_next = 0;
+	}
+}
+
+void Timer::cancel() {
+	if (isActive()) {
+		killTimer(base::take(_timerId));
+	}
+}
+
+TimeMs Timer::remainingTime() const {
+	if (!isActive()) {
+		return -1;
+	}
+	auto now = getms(true);
+	return (_next > now) ? (_next - now) : TimeMs(0);
+}
+
+void Timer::Adjust() {
+	QObject emitter;
+	connect(&emitter, &QObject::destroyed, TimersAdjuster(), &QObject::destroyed);
+}
+
+void Timer::adjust() {
+	auto remaining = remainingTime();
+	if (remaining >= 0) {
+		cancel();
+		_timerId = startTimer(remaining, _type);
+		_adjusted = true;
+	}
+}
+
+void Timer::setTimeout(TimeMs timeout) {
+	Expects(timeout >= 0 && timeout <= std::numeric_limits<int>::max());
+	_timeout = static_cast<unsigned int>(timeout);
+}
+
+int Timer::timeout() const {
+	return _timeout;
+}
+
+void Timer::timerEvent(QTimerEvent *e) {
+	if (_repeat == Repeat::Interval) {
+		if (_adjusted) {
+			start(_timeout, _type, _repeat);
+		} else {
+			_next = getms(true) + _timeout;
+		}
+	} else {
+		cancel();
+	}
+
+	if (_callback) {
+		_callback();
+	}
+}
+
+int DelayedCallTimer::call(TimeMs timeout, lambda_once<void()> callback, Qt::TimerType type) {
+	Expects(timeout >= 0);
+	if (!callback) {
+		return 0;
+	}
+	auto timerId = startTimer(static_cast<int>(timeout), type);
+	if (timerId) {
+		_callbacks.emplace(timerId, std::move(callback));
+	}
+	return timerId;
+}
+
+void DelayedCallTimer::cancel(int callId) {
+	if (callId) {
+		killTimer(callId);
+		_callbacks.erase(callId);
+	}
+}
+
+void DelayedCallTimer::timerEvent(QTimerEvent *e) {
+	auto timerId = e->timerId();
+	killTimer(timerId);
+
+	auto it = _callbacks.find(timerId);
+	if (it != _callbacks.end()) {
+		auto callback = std::move(it->second);
+		_callbacks.erase(it);
+
+		callback();
+	}
+}
+
+} // namespace base
diff --git a/Telegram/SourceFiles/base/timer.h b/Telegram/SourceFiles/base/timer.h
new file mode 100644
index 0000000000..841182c7f5
--- /dev/null
+++ b/Telegram/SourceFiles/base/timer.h
@@ -0,0 +1,108 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+#include "base/lambda.h"
+#include "base/observer.h"
+
+namespace base {
+
+class Timer final : private QObject {
+public:
+	Timer(base::lambda<void()> callback = base::lambda<void()>());
+
+	static Qt::TimerType DefaultType(TimeMs timeout) {
+		constexpr auto kThreshold = TimeMs(1000);
+		return (timeout > kThreshold) ? Qt::CoarseTimer : Qt::PreciseTimer;
+	}
+
+	void setCallback(base::lambda<void()> callback) {
+		_callback = std::move(callback);
+	}
+
+	void callOnce(TimeMs timeout) {
+		callOnce(timeout, DefaultType(timeout));
+	}
+
+	void callEach(TimeMs timeout) {
+		callEach(timeout, DefaultType(timeout));
+	}
+
+	void callOnce(TimeMs timeout, Qt::TimerType type) {
+		start(timeout, type, Repeat::SingleShot);
+	}
+
+	void callEach(TimeMs timeout, Qt::TimerType type) {
+		start(timeout, type, Repeat::Interval);
+	}
+
+	bool isActive() const {
+		return (_timerId != 0);
+	}
+
+	void cancel();
+	TimeMs remainingTime() const;
+
+	static void Adjust();
+
+protected:
+	void timerEvent(QTimerEvent *e) override;
+
+private:
+	enum class Repeat {
+		Interval   = 0,
+		SingleShot = 1,
+	};
+	void start(TimeMs timeout, Qt::TimerType type, Repeat repeat);
+	void adjust();
+
+	void setTimeout(TimeMs timeout);
+	int timeout() const;
+
+	base::lambda<void()> _callback;
+	TimeMs _next = 0;
+	int _timeout = 0;
+	int _timerId = 0;
+
+	Qt::TimerType _type : 2;
+	bool _adjusted : 1;
+	Repeat _repeat : 1;
+
+};
+
+class DelayedCallTimer final : private QObject {
+public:
+	int call(TimeMs timeout, lambda_once<void()> callback) {
+		return call(timeout, std::move(callback), Timer::DefaultType(timeout));
+	}
+
+	int call(TimeMs timeout, lambda_once<void()> callback, Qt::TimerType type);
+	void cancel(int callId);
+
+protected:
+	void timerEvent(QTimerEvent *e) override;
+
+private:
+	std::map<int, lambda_once<void()>> _callbacks; // Better to use flatmap.
+
+};
+
+} // namespace base
\ No newline at end of file
diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h
index df28c96887..82483eca73 100644
--- a/Telegram/SourceFiles/config.h
+++ b/Telegram/SourceFiles/config.h
@@ -124,7 +124,6 @@ enum {
 	SaveDraftTimeout = 1000, // save draft after 1 secs of not changing text
 	SaveDraftAnywayTimeout = 5000, // or save anyway each 5 secs
 	SaveCloudDraftIdleTimeout = 14000, // save draft to the cloud after 14 more seconds
-	SaveCloudDraftTimeout = 1000, // save draft to the cloud with 1 sec extra delay
 	SaveDraftBeforeQuitTimeout = 1500, // give the app 1.5 secs to save drafts to cloud when quitting
 
 	SetOnlineAfterActivity = 30, // user with hidden last seen stays online for such amount of seconds in the interface
diff --git a/Telegram/SourceFiles/mtproto/sender.h b/Telegram/SourceFiles/mtproto/sender.h
index 4b4ee8f84a..05bccacfe0 100644
--- a/Telegram/SourceFiles/mtproto/sender.h
+++ b/Telegram/SourceFiles/mtproto/sender.h
@@ -362,7 +362,7 @@ private:
 	}
 
 	gsl::not_null<Instance*> _instance;
-	std::set<RequestWrap, RequestWrapComparator> _requests;
+	std::set<RequestWrap, RequestWrapComparator> _requests; // Better to use flatmap.
 
 };
 
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index 00398363d7..421f61a24b 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -10,8 +10,10 @@
 <(src_loc)/base/qthelp_url.h
 <(src_loc)/base/runtime_composer.cpp
 <(src_loc)/base/runtime_composer.h
-<(src_loc)/base/task_queue.h
 <(src_loc)/base/task_queue.cpp
+<(src_loc)/base/task_queue.h
+<(src_loc)/base/timer.cpp
+<(src_loc)/base/timer.h
 <(src_loc)/base/type_traits.h
 <(src_loc)/base/variant.h
 <(src_loc)/base/virtual_method.h