|
function detectBrowserCapabilities(){ $("userAgent").innerHTML = navigator.userAgent; var hasWebWorkers = !!window.Worker; $("workersFlag").innerHTML = "" + hasWebWorkers; var hasGeolocation = !!navigator.geolocation; $("geoFlag").innerHTML = "" + hasGeolocation; if (hasGeolocation){ document.styleSheets[0].cssRules[1].style.display = "block"; navigator.geolocation.getCurrentPosition(function(location) { $("geoLat").innerHTML = location.coords.latitude; $("geoLong").innerHTML = location.coords.longitude; }); } var hasDb = !!/*window.open*/Database; $("dbFlag").innerHTML = "" + hasDb; var videoElement = document.createElement("video"); var hasVideo = !!videoElement["canPlayType"]; var ogg = false; var h264 = false; if (hasVideo) { ogg = videoElement.canPlayType('video/ogg; codecs="theora, vorbis"') || "no"; h264 = videoElement.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"') || "no"; } $("videoFlag").innerHTML = "" + hasVideo; if (hasVideo){ var vStyle = document.styleSheets[0].cssRules[0].style; vStyle.display = "block"; } $("h264Flag").innerHTML = "" + h264; $("oggFlag").innerHTML = "" + ogg; } |
HTML5에는 새로운 기능과 표준이 많이 추가되었다. 이 기사에서는 몇 가지 유용한 기능만을 중점적으로 살펴본다. Listing 1에 있는 스크립트를 통해 다음과 같은 네 가지 기능을 확인할 수 있다.
먼저, 이 스크립트는 사용 중인 브라우저의 사용자 에이전트를 표시한다. 이 에이전트는 일반적으로 문자열이며 쉽게 위조될 수 있기는 해도 브라우저를 고유하게 식별하기 위해 사용된다. 이 애플리케이션에서는 그대로 사용하기로 한다. 다음 단계에서는 기능을 확인하기 시작한다. 먼저, 글로벌 범위(창)에서 Worker
함수를 찾아서 웹 작업자를 확인한다. 이 과정에서 다소 관용적인 xxJavascript, 즉 이중 부정을 사용한다. Worker
함수가 존재하지 않는 경우, window.Worker
는 정의되지 않음으로 평가되며 이는 xxJavascript에서 "false" 값으로 표현된다. 이 앞에 단일 부정을 삽입하면 true로 평가되므로 이중 부정은 false로 평가된다. 이 값을 테스트한 다음에는 Listing 2에 있는 DOM 구조를 수정하여 해당 화면에 평가 내용을 출력한다.
<input type="button" value="Begin detection" [안내]태그제한으로등록되지않습니다-xxonclick="detectBrowserCapabilities()"/> <div>Your browser's user-agent: <span id="userAgent"> </span></div> <div>Web Workers? <span id="workersFlag"></span></div> <div>Database? <span id="dbFlag"></span></div> <div>Video? <span id="videoFlag"></span></div> <div class="videoTypes">Can play H.264? <span id="h264Flag"> </span></div> <div class="videoTypes">Can play OGG? <span id="oggFlag"> </span></div> <div>Geolocation? <span id="geoFlag"></span></div> <div class="location"> <div>Latitude: <span id="geoLat"></span></div> <div>Longitude: <span id="geoLong"></span></div> </div> |
Listing 2는 기능 확인 스크립트에서 수집한 진단 정보를 표시하기 위해 사용하는 간단한 HTML 구조이다. Listing 1에서 알 수 있는 바와 같이 다음에는 위치정보를 테스트한다. 한 번 더 이중 부정 기술을 사용하지만 이번에는 navigator
오브젝트의 특성인 geolocation
오브젝트를 확인한다. geolocation 오브젝트가 있으면 geolocation
오브젝트의 getCurrentPosition
함수를 사용하여 현재의 위치를 가져온다. 일반적으로 위치정보를 가져오는 과정에서 Wi-Fi 네트워크를 스캔하기 때문에 이 과정은 느릴 수 있다. 또한, 모바일 장치에서는 위치정보를 가져오는 과정에서 셀 타워(기지국)를 스캔하고 ping을 실행하여 GPS 위성과의 연결 상태를 확인한다. 이 과정은 오래 걸릴 수 있기 때문에 getCurrentPosition
함수는 비동기로 작동하며 매개변수로 콜백
함수를 취한다. 이런 경우에는 해당 CSS를 전환함으로써 위치 필드를 간단히 표시하는 콜백
함수에 클로저를 사용하여 DOM에 경도와 위도를 작성한다.
다음 단계에서는 데이터베이스 스토리지를 확인한다. 전역 함수 openDatabase
가 있는지 확인한다. 이 함수는 클라이언트 측 데이터베이스를 작성하고 액세스하는 데 사용한다.
마지막으로 기본적인 비디오 재생을 확인한다. DOM API를 사용하여 비디오 요소를 작성한다. 현재는 모든 브라우저에서 이러한 요소를 작성할 수 있다. 이전의 브라우저에서도 비디오 요소는 유효한 DOM 요소이지만 특별한 의미는 없다. foo
요소를 작성하는 것과 비슷하다. 최신 브라우저에서는 비디오 요소가 div
나 table
요소를 작성하는 것과 같이 특수 요소이다. 이 요소에는 canPlayType
함수가 있어서 비디오 요소의 존재를 간단히 확인한다.
브라우저에 기본적인 비디오 재생 기능이 있어도 재생할 수 있는 지원 코덱이나 비디오 유형은 표준화되지 않았다. 브라우저에서 지원되는 코덱을 확인하고자 할 수도 있다. 코덱에는 표준 목록이 없지만 가장 일반적으로 사용하는 두 가지 코덱은 H.264와 Vorbis이다. 특정 코덱을 지원하는지 확인하려면 canPlayType
함수에 식별 문자열을 전달해야 한다. 브라우저에서 해당 코덱을 지원하면 이 함수는 probably
를 리턴한다. (농담이 아니라 진짜다.) 해당 코덱을 지원하지 않는 경우에는 널을 리턴한다. 기능 확인 코드에서는 이러한 값을 간단히 확인하여 DOM에 해당 응답을 표시한다. 일반적으로 사용하는 브라우저에서 이 코드를 테스트한 다음, Listing 3에서 수집된 결과를 표시한다.
#Firefox 3.6 Your browser's user-agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6 Web Workers? true Database? false Video? true Can play H.264? no Can play OGG? probably Geolocation? true Latitude: 37.2502812 Longitude: -121.9059866 #Safari 4.0.4 Your browser's user-agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-us) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10 Web Workers? true Database? true Video? true Can play H.264? probably Can play OGG? no Geolocation? false #Chrome 5.0.322 Your browser's user-agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/533.1 (KHTML, like Gecko) Chrome/5.0.322.2 Safari/533.1 Web Workers? true Database? true Video? true Can play H.264? no Can play OGG? no Geolocation? false |
위에 있는 일반적으로 사용하는 모든 데스크탑 브라우저는 매우 많은 기능을 지원한다.
데스크탑 브라우저에서는 일부만 위치정보를 지원하지만 모바일 브라우저에서는 대부분 위치정보를 지원한다. Listing 4에는 모바일 브라우저에 대한 수집 결과가 표시되어 있다.
#iPhone 3.1.3 Simulator Your browser's user-agent: Mozilla/5.0 (iPhone Simulator; U; CPU iPhone OS 3.1.3 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7E18 Safari/528.16 Web Workers? false Database? true Video? true Can play H.264? maybe Can play OGG? no Geolocation? true Latitude: 37.331689 Longitude: -122.030731 #Android 1.6 Emulator Your browser's user-agent: Mozilla/5.0 (Linux; Android 1.6; en-us; sdk Build/Donut) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1 Web Workers? false Database? false Video? false Geolocation? false #Android 2.1 Emulator Your browser's user-agent: Mozilla/5.0 (Linux; U; Android 2.1; en-us; sdk Build/ERD79) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 Web Workers? true Database? true Video? true Can play H.264? no Can play OGG? no Geolocation? true Latitude: Longitude: |
위에는 최신 iPhone 시뮬레이터 중 하나와 두 가지 유형의 Android가 표시되어 있다. Android 1.6에서는 이 기사에서 테스트한 모든 기능을 지원한다. 사실상 Android 1.6에서는 비디오를 제외한 모든 기능을 지원하지만 Google Gears를 사용하여 이러한 기능을 지원한다. Google Gears는 API와 기능이 동일하지만 웹 표준을 따르지 않기 때문에 Listing 4와 같은 결과를 얻게 된다. 이 결과를 Android 2.1에 대한 결과와 비교해 보면 Android 2.1에서는 모든 기능이 지원된다는 것을 알 수 있다.
iPhone에서는 Web werkers를 제외한 모든 기능을 지원한다. Listing 3에서 알 수 있는 바와 같이 Safari 데스크탑 버전에서는 웹 작업자를 지원하므로 iPhone에도 이 기능이 곧 도입될 것으로 보인다.
이제까지 사용자 브라우저의 기능을 확인하는 방법을 알아보았으므로 사용자 브라우저에서 처리할 수 있는 기능에 따라 이러한 몇 가지 기능을 조합하여 사용하는 간단한 애플리케이션을 살펴보도록 하자. Foursquare API를 사용하여 사용자가 있는 근처에서 인기있는 장소를 찾아주는 애플리케이션을 작성한다.
이 예제에서는 모바일 장치에서 위치정보를 사용하는 방법을 집중적으로 살펴보겠지만 위치정보는 Firefox 3.5 이상에서만 지원된다는 점을 기억해야 한다. 이 애플리케이션은 먼저 현재 사용자가 있는 위치에서 가까운 장소(Foursquare에서는 Venue라고 부름)를 검색한다. 장소는 어느 곳이나 될 수 있지만 일반적으로 레스토랑, 술집, 상점 등이 해당한다. 이 예제는 웹 애플리케이션이기 때문에 동일 출처 정책(Same Origin Policy)에 따라 제한된다. 이 애플리케이션은 Foursquare의 API를 직접 호출할 수 없다. 여기서는 이러한 호출을 본질적으로 프록시하기 위해 Java 서블릿을 사용한다. 여기에서는 Java와 관련된 특별한 사항이 없으며 PHP와 Python, Ruby 등을 사용하여 비슷한 프록시를 쉽게 작성할 수 있다. Listing 5에는 프록시 서블릿이 표시되어 있다.
public class FutureWebServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String operation = request.getParameter("operation"); if (operation != null && operation.equalsIgnoreCase("getDetails")){ getDetails(request,response); } String geoLat = request.getParameter("geoLat"); String geoLong = request.getParameter("geoLong"); String baseUrl = "http://api.foursquare.com/v1/venues.json?"; String urlStr = baseUrl + "geolat=" + geoLat + "&geolong=" + geoLong; PrintWriter out = response.getWriter(); proxyRequest(urlStr, out); } private void proxyRequest(String urlStr, PrintWriter out) throws IOException{ try { URL url = new URL(urlStr); InputStream stream = url.openStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(stream)); String line = ""; while (line != null){ line = reader.readLine(); if (line != null){ out.append(line); } } out.flush(); stream.close(); } catch (MalformedURLException e) { e.printStackTrace(); } } private void getDetails(HttpServletRequest request, HttpServletResponse response) throws IOException{ String venueId = request.getParameter("venueId"); String urlStr = "http://api.foursquare.com/v1/venue.json?vid="+venueId; proxyRequest(urlStr, response.getWriter()); } } |
여기서 중요한 사항은 두 가지 Foursquare API를 프록시한다는 점이다. 하나는 검색을 위한 것이며 다른 하나는 장소에 대한 세부사항을 가져오기 위한 것이다. 이 두 가지 API를 구별하기 위해 이러한 details API는 operation 매개변수를 추가한다. 또한, 리턴 유형으로 JSON을 지정하며 이렇게 하면 xxJavascript에서 데이터를 구문 분석하기가 수월하다. 이제까지 애플리케이션 코드에서 작성할 수 있는 호출 유형을 알아보았으므로, 이제 애플리케이션에서 이러한 API를 호출하여 Foursquare 데이터를 사용하는 방법을 살펴본다.
먼저 검색 함수를 호출한다. Listing 5에서 위도와 경도에 해당하는 두 개의 매개변수, geoLat
와 geoLong
이 필요하다는 점을 알 수 있다. 아래 Listing 6에는 애플리케이션에서 이러한 데이터를 가져오는 방법과 해당 서블릿을 호출하는 방법이 표시되어 있다.
Listing 6. 위치를 사용하여 검색 함수 호출하기
if (!!navigator.geolocation){ navigator.geolocation.getCurrentPosition(function(location) { venueSearch(location.coords.latitude, location.coords.longitude); }); } var allVenues = []; function venueSearch(geoLat, geoLong){ var xhr = new XMLHttpRequest(); xhr.[안내]태그제한으로등록되지않습니다-onreadystatechange = function(){ if (this.readyState == 4 && this.status == 200){ var responseObj = eval('(' + this.responseText + ')'); var venues = responseObj.groups[0].venues; allVenues = venues; buildVenuesTable(venues); } } xhr.open("GET", "api?geoLat=" + geoLat + "&geoLong="+geoLong); xhr.send(null); } |
위에 있는 코드는 브라우저에 위치정보가 있는지 확인한다. 위치정보가 있으면 이 코드는 해당 위치정보를 가져오고 위도와 경도를 사용하여 venueSearch
함수를 호출한다. 이 함수에서는 Ajax(Listing 5에 있는 서블릿을 호출하는 XMLHttpRequest
오브젝트)를 사용한다. 이 함수는 콜백
함수에 클로저를 사용하여 Fousquare의 JSON 데이터를 구문 분석한 다음, venue 오브젝트 배열을 아래에 있는 buildVenuesTable
을 호출한 함수에 전달한다.
Listing 7. 장소 정보를 사용하여 UI 빌드하기
function buildVenuesTable(venues){ var rows = venues.map(function (venue) { var row = document.createElement("tr"); var nameTd = document.createElement("td"); nameTd.appendChild(document.createTextNode(venue.name)); row.appendChild(nameTd); var addrTd = document.createElement("td"); var addrStr = venue.address + " " + venue.city + "," + venue.state; addrTd.appendChild(document.createTextNode(addrStr)); row.appendChild(addrTd); var distTd = document.createElement("td"); distTd.appendChild(document.createTextNode("" + venue.distance)); row.appendChild(distTd); return row; }); var vTable = document.createElement("table"); vTable.border = 1; var header = document.createElement("thead"); var nameLabel = document.createElement("td"); nameLabel.appendChild(document.createTextNode("Venue Name")); header.appendChild(nameLabel); var addrLabel = document.createElement("td"); addrLabel.appendChild(document.createTextNode("Address")); header.appendChild(addrLabel); var distLabel = document.createElement("td"); distLabel.appendChild(document.createTextNode("Distance (m)")); header.appendChild(distLabel); vTable.appendChild(header); var body = document.createElement("tbody"); rows.forEach(function(row) { body.appendChild(row); }); vTable.appendChild(body); $("searchResults").appendChild(vTable); if (!!/*window.open*/Database){ $("saveBtn").style.display = "block"; } } |
Listing 7에 있는 코드는 대부분 장소 정보를 사용하여 데이터 테이블을 작성하는 코드이다. 하지만 여기에는 몇 가지 흥미로운 사항이 있다. 배열 오브젝트 맵 및 forEach
함수와 같은 고급 xxJavascript 기능을 사용했다는 점에 주목하자. 이러한 기능은 위치정보를 지원하는 모든 브라우저에서 사용할 수 있다. 또한, 마지막 두 개의 행도 흥미롭다. 이 행에서 데이터베이스 지원 여부가 확인된다. 데이터베이스를 지원하는 경우에는 Save 단추를 사용할 수 있으며 사용자는 이 단추를 클릭하여 이러한 장소 데이터를 모두 로컬 데이터베이스에 저장할 수 있다. 다음 섹션에서는 이 과정이 어떻게 수행되는지 살펴본다.
Listing 7에서 고전적이고 진보적 개선 전략을 확인할 수 있다. 이 예제는 데이터베이스 지원 여부를 테스트한다. 데이터베이스가 지원하는 경우, 이 코드는 데이터베이스 기능을 사용하는 애플리케이션에 새로운 기능을 삽입하는 UI 요소를 추가한다. 이 경우에는 하나의 단추가 추가된다. 이 단추를 클릭하면 Listing 8에 있는 saveAll
함수가 호출된다.
var db = {}; function saveAll(){ db = /*window.open*/Database("venueDb", "1.0", "Venue Database",1000000); db.transaction(function(txn){ txn.executeSql("CREATE TABLE venue (id INTEGER NOT NULL PRIMARY KEY, "+ "name NVARCHAR(200) NOT NULL, address NVARCHAR(100), cross_street NVARCHAR(100), "+ "city NVARCHAR(100), state NVARCHAR(20), geolat TEXT NOT NULL, "+ "geolong TEXT NOT NULL);"); }); allVenues.forEach(saveVenue); countVenues(); } function saveVenue(venue){ // check if we already have the venue db.transaction(function(txn){ txn.executeSql("SELECT name FROM venue WHERE id = ?", [venue.id], function(t, results){ if (results.rows.length == 1 && results.rows.item(0)['name']){ console.log("Already have venue id=" + venue.id); } else { insertVenue(venue); } }) }); } function insertVenue(venue){ db.transaction(function(txn){ txn.executeSql("INSERT INTO venue (id, name, address, cross_street, "+ "city, state, geolat, geolong) VALUES (?, ?, ?, ?, "+ "?, ?, ?, ?);", [venue.id, venue.name, venue.address, venue.crossstreet, venue.city, venue.state, venue.geolat, venue.geolong], null, errHandler); }); } function countVenues(){ db.transaction(function(txn){ txn.executeSql("SELECT COUNT(*) FROM venue;",[], function(transaction, results){ var numRows = results.rows.length; var row = results.rows.item(0); var cnt = row["COUNT(*)"]; alert(cnt + " venues saved locally"); }, errHandler); }); } |
데이터베이스에 장소 데이터를 저장하려면, 먼저 데이터를 저장할 테이블을 작성해야 한다. 테이블을 작성하는 SQL 구문은 거의 표준화되어 있다. (데이터베이스를 지원하는 모든 브라우저에서는 SQLite를 사용한다. 지원하는 데이터 유형, 제한조건 등은 SQLite 문서를 참조한다. SQL 실행은 비동기적으로 수행된다. 트랜잭션 함수가 호출되고 콜백 함수가 이 함수에 전달된다. 콜백
함수는 SQL을 실행하는 데 사용할 수 있는 트랜잭션 오브젝트를 가져온다. executeSQL
함수는 SQL 문자열과 매개변수 목록(선택적), 성공 및 오류 핸들러 함수를 받는다. 오류 핸들러가 없으면 오류가 표시되지 않는다. create table
명령문에는 이러한 오류 핸들러가 필요하다. 처음 이 스크립트를 실행하면 테이블이 올바르게 작성된다. 두 번째로 이 스크립트를 실행하면 테이블이 이미 있기 때문에 이 스크립트는 실패한다. 그러나 괜찮다. 테이블에 행을 삽입하기 전에 데이터베이스에 테이블이 있는지 확인하기 위한 과정일 뿐이다.
테이블을 작성한 후에는 forEach
함수를 사용하여 Foursquare에서 리턴된 각 장소와 함께 saveVenue
함수를 호출한다. 이 함수는 쿼리를 통해 먼저 장소가 이미 로컬에 저장되었는지 확인한다. 여기에서 성공 핸들러를 사용했다는 것을 알 수 있다. 쿼리를 통해 얻은 결과 세트는 핸들러에게 전달된다. 결과가 없거나 장소가 아직 로컬에 저장되지 않은 경우에는 삽입 명령문을 수행하는 insertVenue
함수가 호출된다.
saveAll
함수를 사용하여 장소를 저장하거나 삽입한 후에는 countVenues
함수를 호출한다. 이 함수는 쿼리를 통해 venue 테이블에 삽입된 행의 총 수를 확인한다. 여기서 row["COUNT(*)"]
구문은 쿼리 결과 세트에서 수를 산출한다.
이제까지 브라우저에서 데이터베이스를 지원하는 경우에 데이터베이스 지원을 사용하는 방법을 알아보았으므로 다음 섹션에서는 웹 작업자 지원을 사용하는 방법을 살펴보도록 하자.
Listing 6으로 돌아가서 몇 가지를 수정하도록 하자. 아래 Listing 9에서와 같이 웹 작업자가 지원되는지 확인한다. 웹 작업자가 지원되는 경우에는 이 기능을 사용하여 Foursquare에서 검색한 각 장소에 대한 자세한 정보를 가져온다.
function venueSearch(geoLat, geoLong){ var xhr = new XMLHttpRequest(); xhr.[안내]태그제한으로등록되지않습니다-onreadystatechange = function(){ if (this.readyState == 4 && this.status == 200){ var responseObj = eval('(' + this.responseText + ')'); var venues = responseObj.groups[0].venues; allVenues = venues; buildVenuesTable(venues); if (!!window.Worker){ var worker = new Worker("details.js"); worker.onmessage = function(message){ var tips = message.data; displayTips(tips); }; worker.postMessage(allVenues); } } } xhr.open("GET", "api?geoLat=" + geoLat + "&geoLong="+geoLong); xhr.send(null); } |
위에 있는 코드에서는 앞서 살펴보았던 것과 같은 기능 확인 코드를 사용한다. 웹 작업자가 지원되는 경우에는 새로운 Worker를 작성한다. 새로운 Worker를 작성하려면 Worker가 실행할 또 다른 스크립트(이 경우에는 details.js 파일)를 가리키는 URL이 필요하다. 해당 작업을 완료하면 Worker는 기본 스레드에 메시지를 다시 전송한다. 이러한 메시지는 onmessage
핸들러에서 수신하며 이 코드에서는 간단한 클로저를 사용하여 이 핸들러를 처리한다. 마지막으로 Worker를 초기화하기 위해 Worker가 처리할 일부 데이터를 사용하여 postMessage
함수를 호출한다. 이 코드에서는 Foursquare에서 검색한 모든 장소를 전달한다. Listing 10에는 details.js의 내용이 있으며 이 스크립트는 Worker 함수에서 실행된다.
Listing 10. Worker에서 실행되는 스크립트(details.js)
var tips = []; onmessage = function(message){ var venues = message.data; venues.foreach(function(venue){ var xhr = new XMLHttpRequest(); xhr.[안내]태그제한으로등록되지않습니다-onreadystatechange = function(){ if (this.readyState == 4 && this.status == 200){ var venueDetails = eval('(' + this.responseText + ')'); venueDetails.tips.forEach(function(tip){ tip.venueId = venue.id; tips.push(tip); }); } }; xhr.open("GET", "api?operation=getDetails&venueId=" + venueId, true); xhr.send(null); }); postMessage(tips); } |
이 details.js 스크립트는 각 장소를 대상으로 반복된다. 이 스크립트는 각 장소를 대상으로 Foursquare 프록시에 대한 콜백을 작성하고 일반적으로 XMLHttpRequest
를 사용하여 해당 장소의 세부사항을 가져온다. 그러나 open
함수를 사용하여 연결을 여는 경우에는 세 번째 매개변수(true
)를 전달해야 한다. 이렇게 하면 일반적으로 사용되는 비동기식이 아닌 동기식으로 호출하게 된다. 기본 UI 스레드에서 사용하는 것이 아니기 때문에 Worker에서 동기식으로 호출하여도 아무런 문제가 없으며 이렇게 한다고 해서 애플리케이션이 다운되지는 않는다. 동기식으로 호출을 하는 경우에는 다음 호출을 시작하기 전에 각 호출이 완료되어야 한다. 핸들러는 장소 세부사항에서 간단히 팁을 추출하고 이러한 모든 팁을 수집하여 기본 UI 스레드에 다시 전달한다. 이러한 데이터를 다시 전달하기 위해 postMessage
함수를 호출하며 Listing 9에서 알 수 있는 바와 같이 이 함수는 Worker에서 onmessage
콜백 함수를 호출한다.
기본적으로 장소를 검색하면 10개의 장소가 리턴된다. 추가로 열 개를 더 호출하여 해당 세부사항을 가져오려면 얼마나 많은 시간이 걸릴지 상상할 수 있을 것이다. 일반적으로 이러한 유형의 작업은 웹 작업자를 사용하여 백그라운드 스레드로 처리한다.