Ajax在浏览器上具体是如何执行的(2)
上一节把javascript执行流程接介绍完,本章将再介绍一个问题,就是在浏览器端,javascript是如何实现异步的?
要想讲明这个问题,首先就要弄明白一个问题,异步是什么东西.
事实上,异步只不过是在本程序内部,启动另外一个线程执行你要执行的代码,你的程序启动在主线程中,而异步是启动在另外一个线程内。线程事实上只是你将程序执行的顺序,交给操作系统,操作系统,在你需要时候改变CS,IP.语言并不好形容,请看下图
在Source\WebCore\xml\XMLHttpRequest.cpp中,定义了你的请求是同步还是异步的,我们的默认JS代码是异步的,所以会已多线程方式去执行回调函数
在webkit中,启动同时,会执行以下代码,实际上就是创建了一个你看不到的窗口来接受系统的消息TimerWindowWndProc,便是回调函数,函数定义在Source\WebCore\platform\win\SharedTimerWin.cpp
static void initializeOffScreenTimerWindow()
{
if (timerWindowHandle)
return;
WNDCLASSEX wcex;
memset(&wcex, 0, sizeof(WNDCLASSEX));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpfnWndProc = TimerWindowWndProc;
wcex.hInstance = WebCore::instanceHandle();
wcex.lpszClassName = kTimerWindowClassName;
RegisterClassEx(&wcex);
timerWindowHandle = CreateWindow(kTimerWindowClassName, 0, 0,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, HWND_MESSAGE, 0, WebCore::instanceHandle(), 0);
timerFiredMessage = RegisterWindowMessage(L"com.apple.WebKit.TimerFired");
}
这样就多启动了一个线程,用来下载资源或者是其他的操作
在Source\WebCore\platform\network\curl\ResourceHandleManager.cpp中,使用了curl请求资源,就是ajax要发送的的请求
ResourceHandleManager::ResourceHandleManager()
: m_downloadTimer(this, &ResourceHandleManager::downloadTimerCallback)
m_downloadTimer是一个Timer类,这个类可以将要回调的函数加入
timer 定义在D:\WebKit\Source\WebCore\platform\Timer.cpp
template <typename TimerFiredClass> class Timer : public TimerBase {
public:
typedef void (TimerFiredClass::*TimerFiredFunction)(Timer*);
Timer(TimerFiredClass* o, TimerFiredFunction f)
: m_object(o), m_function(f) { }
private:
virtual void fired() { (m_object->*m_function)(this); }
然后利用面提到的线程,将要执行的函数加入
inline void TimerBase::heapInsert()
{
ASSERT(!inHeap());
timerHeap().append(this);
m_heapIndex = timerHeap().size() - 1;
heapDecreaseKey();
}
最终就是执行下面的函数完成请求,downloadTimerCallback
就是加入的函数,它实现了发送参数,接受返回的工作,然后利用上面提到的线程进行操作
void ResourceHandleManager::downloadTimerCallback(Timer<ResourceHandleManager>* timer)
{
startScheduledJobs();
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
int maxfd = 0;
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = selectTimeoutMS * 1000; // select waits microseconds
// Retry 'select' if it was interrupted by a process signal.
int rc = 0;
do {
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
curl_multi_fdset(m_curlMultiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
// When the 3 file descriptors are empty, winsock will return -1
// and bail out, stopping the file download. So make sure we
// have valid file descriptors before calling select.
if (maxfd >= 0)
rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
} while (rc == -1 && errno == EINTR);
if (-1 == rc) {
#ifndef NDEBUG
perror("bad: select() returned -1: ");
#endif
return;
}
int runningHandles = 0;
while (curl_multi_perform(m_curlMultiHandle, &runningHandles) == CURLM_CALL_MULTI_PERFORM) { }
// check the curl messages indicating completed transfers
// and free their resources
while (true) {
int messagesInQueue;
CURLMsg* msg = curl_multi_info_read(m_curlMultiHandle, &messagesInQueue);
if (!msg)
break;
// find the node which has same d->m_handle as completed transfer
CURL* handle = msg->easy_handle;
ASSERT(handle);
ResourceHandle* job = 0;
CURLcode err = curl_easy_getinfo(handle, CURLINFO_PRIVATE, &job);
ASSERT_UNUSED(err, CURLE_OK == err);
ASSERT(job);
if (!job)
continue;
ResourceHandleInternal* d = job->getInternal();
ASSERT(d->m_handle == handle);
if (d->m_cancelled) {
removeFromCurl(job);
continue;
}
if (CURLMSG_DONE != msg->msg)
continue;
if (CURLE_OK == msg->data.result) {
if (!d->m_response.responseFired()) {
handleLocalReceiveResponse(d->m_handle, job, d);
if (d->m_cancelled) {
removeFromCurl(job);
continue;
}
}
if (d->client())
d->client()->didFinishLoading(job, 0);
} else {
char* url = 0;
curl_easy_getinfo(d->m_handle, CURLINFO_EFFECTIVE_URL, &url);
#ifndef NDEBUG
fprintf(stderr, "Curl ERROR for url='%s', error: '%s'\n", url, curl_easy_strerror(msg->data.result));
#endif
if (d->client())
d->client()->didFail(job, ResourceError(String(), msg->data.result, String(url), String(curl_easy_strerror(msg->data.result))));
}
removeFromCurl(job);
}
bool started = startScheduledJobs(); // new jobs might have been added in the meantime
if (!m_downloadTimer.isActive() && (started || (runningHandles > 0)))
m_downloadTimer.startOneShot(pollTimeSeconds);
}