로컬포리지: 개선된, 오프라인 저장공간

웹앱은 거대한 데이터세트와 바이너리 파일을 한동안 저장하는 것과 같은 오프라인 기능을 갖고 있습니다. 여러분은 MP3 파일 캐시같은 방식도 쓸 수 있습니다. 브라우저 기술은 데이터를 오프라인으로 저장할 수 있고 그 방식은 여러가지입니다. 다만 문제는, 그러기 위한 방법들이 일관성있게 선택되지 않는다는 점입니다.

localstorage는 기본적인 데이터 저장공간(storage)을 제공하지만, 느리고 바이너리 블롭(binary blobs)을 다룰 수 없습니다. indexedDBWebSQL은 비동기(asynchoronus)에, 빠르고, 거대한 데이터세트를 지원하나, 그 API는 전혀 단순하지 않습니다. 게다가 indexedDBWebSQL은 주요 브라우저 제조사들이 모두 지원하지도 않으며, 이런 사정이 곧 바뀔 것 같지도 않습니다.

만일 여러분들이 오프라인 기능을 지원하는 웹앱을 만들어야 하는데 어느 지점부터 시작해야할 지 모르겠다면, 이 글이 여러분을 위한 것입니다. 만일 과거에 오프라인 지원 기능을 만들다가 당신의 꼭지가 돌아버렸다면, 이 글은 그런 당신을 위한 글이기도 합니다. 모질라는 로컬포리지(localForage)라 부르는 라이브러리를 만들었습니다. 어떤 브라우저에서든지 데이터를 오프라인에 저장하는 일을 훨씬 더 수월하게 해줍니다.

어라운드(around)는 제가 만든 HTML5기반 포스퀘어 클라이언트입니다. 이게 오프라인 저장 기능을 다룰 때 힘든 일을 겪어보는 데 도움이 됐습니다. 우리는 아직도 로컬포리지를 사용하는 방법을 고안해내는 중입니다만, 여러분에게 코드를 정독하는 것과 같은 방식을 통해 보여드릴 만한 자료가 있습니다.

로컬포리지는 매우 단순한 로컬스토리지API를 사용하는 자바스크립트 라이브러리입니다. localStorage는 get, set, remove, clear, length 등 필수적인 기능을 갖췄는데, 여기에 다음 사항을 추가했습니다:

  • 콜백(callbacks)을 포함한 비동기 API
  • (자동으로 관리되며; 최상의 드라이버가 불러들여지는) IndexedDB, WebSQL, localStorage 드라이버
  • 이미지, 파일, 이밖의 것을 저장할 수 있는Blob와 임의의 타입 지원.
  • ES6 Promises 지원

역자 주 : ES6 Promises란 차세대 자바스크립트 표준(ECMAScript 6)에 포함된 비동기 프로그래밍 기능 Promise를 가리킵니다. Promise는 프로그래밍 언어에서 비동기 작업의 최종 결과를 나타내며, 어떤 작업의 성공적인 결과값 또는 실패한 원인을 드러내는 역할을 합니다.

IndexedDBWebSQL을 포함한 지원은 여러분이 웹앱에 localStorage만 쓸 때 허용되는 것보다 더 많은 데이터를 저장할 수 있게 해 줍니다. 그 API에 포함된 논블로킹 특성(non-blocking nature)은 여러분의 앱이 get/set 호출에 대한 메인 쓰레드에 걸리지 않게 함으로써 더 빨라지게 만듭니다. promises 지원은 콜백의 늪에 빠지지 않고 자바스크립트 코드를 작성하는 즐거움을 선사합니다. 물론, 여러분이 콜백 애호가일 경우 로컬포리지는 그 역시 지원합니다.

백문이 불여일견!

기존 localStorage API는 여러모로 사실 매우 훌륭합니다; 쓰기 편하고, 복잡한 데이터 구조를 강요하지도 않고, 장식 코드(boilerplate)를 요구하지도 않습니다. 많일 여러분이 앱에서 저장하기 원하는 정보를 설정하려 한다면, 작성해야 할 코드는 다음 것이 전부입니다:

// Our config values we want to store offline.
var config = {
    fullName: document.getElementById('name').getAttribute('value'),
    userId: document.getElementById('id').getAttribute('value')
};
 
// Let's save it for the next time we load the app.
localStorage.setItem('config', JSON.stringify(config));
 
// The next time we load the app, we can do:
var config = JSON.parse(localStorage.getItem('config'));

우리가 localStorage에서 값을 문자열로 저장해야 한다는 점을 염두에 두십시오. 우리는 이것과 상호작용할 때 JSON으로/에서 변환을 해야 합니다.

이는 반갑게도 참 수월해 보입니다만, 여러분은 즉시 localStorage와 관련된 몇가지 문제점을 깨닫게 됩니다.

  1. 비동기적입니다. 우리는 데이터가 얼마나 거대하건간에, 그게 디스크에서 읽히고 파싱되기 전까지는 기다립니다. 이는 우리 앱의 응답 속도를 떨어뜨립니다. 이는 특히 모바일 기기에서 나쁜 점입니다; 주요 데이터가 모두 전송되기 전까지 메인 쓰레드가 멈춰 있기 때문에 여러분의 앱이 느리거나 응답하지 않는 것처럼 보입니다.
  2. 문자열만 지원합니다. 우리가 JSON.parseJSON.stringify를 어떻게 써야 하는지 알아차리셨습니까? localStorage가 자바스크립트 문자열만을 지원하기 때문에 벌어지는 일입니다. 숫자, 불린(booleans), 블롭(Blobs) 등은 없습니다. 이 점은 숫자나 배열을 저장할 때 짜증을 유발하는 정도입니다만, 블롭 저장을 아예 불가능하게(또는 적어도 매우 열받게 하거나 느리게) 만드는 것에 효과적입니다.

로컬포리지를 통한 더 나은 방법

로컬포리지는 로컬스토리지API 대신 비동기API를 사용함으로써 2가지 문제를 모두 해결했습니다. 같은 데이터를 저장하기 위해 IndexedDB를 쓰는 경우와 로컬포리지를 쓰는 경우를 비교해 보십시오:

IndexedDB 코드

// IndexedDB.
var db;
var dbName = "dataspace";
 
var users = [ {id: 1, fullName: 'Matt'}, {id: 2, fullName: 'Bob'} ];
 
var request = indexedDB.open(dbName, 2);
 
request.onerror = function(event) {
    // Handle errors.
};
request.onupgradeneeded = function(event) {
    db = event.target.result;
 
    var objectStore = db.createObjectStore("users", { keyPath: "id" });
 
    objectStore.createIndex("fullName", "fullName", { unique: false });
 
    objectStore.transaction.oncomplete = function(event) {
        var userObjectStore = db.transaction("users", "readwrite").objectStore("users");
    }
};
 
// Once the database is created, let's add our user to it...
 
var transaction = db.transaction(["users"], "readwrite");
 
// Do something when all the data is added to the database.
transaction.oncomplete = function(event) {
    console.log("All done!");
};
 
transaction.onerror = function(event) {
    // Don't forget to handle errors!
};
 
var objectStore = transaction.objectStore("users");
 
for (var i in users) {
    var request = objectStore.add(users[i]);
    request.onsuccess = function(event) {
        // Contains our user info.
        console.log(event.target.result);
    };
}

WebSQL은 장황하지 않습니다만, 여전히 일부 장식 코드를 필요로 합니다. 로컬포리지로 쓸 경우 여러분은 이렇게만 쓰면 됩니다.

로컬포리지 코드

// Save our users.
var users = [ {id: 1, fullName: 'Matt'}, {id: 2, fullName: 'Bob'} ];
localForage.setItem('users', users, function(result) {
    console.log(result);
});

이처럼 일이 좀 줄어듭니다.

문자열 대신 다른 데이터

여러분이 앱을 위해 사용자 프로파일 이미지를 내려받고 오프라인에 캐시를 두려고 한다 칩시다. 로컬포리지를 통해 바이너리 데이터를 저장하면 쉬운 일입니다.

// We'll download the user's photo with AJAX.
var request = new XMLHttpRequest();
 
// Let's get the first user's photo.
request.open('GET', "/users/1/profile_picture.jpg", true);
request.responseType = 'arraybuffer';
 
// When the AJAX state changes, save the photo locally.
request.addEventListener('readystatechange', function() {
    if (request.readyState === 4) { // readyState DONE
        // We store the binary data as-is; this wouldn't work with localStorage.
        localForage.setItem('user_1_photo', request.response, function() {
            // Photo has been saved, do whatever happens next!
        });
    }
});
 
request.send()

이 다음에는 코드 3줄이면 로컬포리지에서 사진을 꺼낼 수 있게 됩니다.

localForage.getItem('user_1_photo', function(photo) {
    // Create a data URI or something to put the photo in an img tag or similar.
    console.log(photo);
});

콜백과 promises

만일 여러분이 코드에 콜백을 쓰기 싫다면 로컬포리지에서 콜백 선언을 하는 대신 ES6 Promises를 쓸 수 있습니다. 마지막 예제에서 콜백 대신 promises를 써서 사진을 불러와 봅시다.

localForage.getItem('user_1_photo').then(function(photo) {
    // Create a data URI or something to put the photo in an <img> tag or similar.
    console.log(photo);
});

보시는대로, 이건 약간 인위적인 예시입니다만, 어라운드는 어느정도 여러분이 매일 사용하는 라이브러리에서 봤음직한 현실 세계의 코드를 포함하고 있습니다.

크로스브라우저 지원

로컬포리지는 모든 최신 브라우저를 지원합니다. IndexedDB사파리를 제외한 최신 브라우저를 지원합니다 (IE 10+, IE 모바일 10+, 파이어폭스 10+, 안드로이드용 파이어폭스 25+, 크롬 23+, 안드로이드용 크롬 32+, and 오페라15+). 이와 별개로 순정(stock) 안드로이드 브라우저(2.1+)와 사파리는 WebSQL을 사용합니다.

최악의 경우, 로컬스토리지는 로컬스토리지는 폴백(fall back)하므로, 여러분은 적어도 오프라인에 기본적인 데이터를 (블롭을 통하지 않고 훨씬 느리지도 않게) 저장할 수 있습니다. 최소한 로컬스토리지의 데이터 저장 방식으로 필요한 JSON 문자열로/에서 여러분의 데이터를 자동으로 변환하는 부분을 처리해 줍니다.

기트허브에서 로컬포리지에 대해 더 알아보시고, 만일 더 많은 일을 하는 라이브러리를 보고 싶다면 이슈를 등록해 주세요!

원저자: tofumatt, Robert Nyman, Angelina Fabbro – 원문으로 가기

작성자: Mincheol Im

https://twitter.com/imc84

Mincheol Im가 작성한 문서들…


1개 댓글

  1. 왕종선

    localForage 에서 exists 등의 기능도 지원이 되면 좋을것 같습니다. 현재 버전인 1.3.0 에서는 지원이 안되 조금 아쉽네요.

    10월 14th, 2015 at 5:23 오후

댓글 쓰기