본문 바로가기
개발/dart

8. Use Streams for Data

by 허허 그림 2014. 3. 2.
728x90


* 전체 링크

1.get started

2. Connect Dart & HTML

3. Add Elements to the DOM

4, Remove DOM Elements

- 5. Install Shared Packages

6. Define a Custom Element

7. Use Future-Based APIS

8. Use Streams for Data

9. Fetch Data Dynamically

10. Get Input from a Form

11. Use Indexed DB

12. Write Command-line Apps


Use Streams for Data

데이타 스트림 이용하기

Use streams to handle sequences of data.
데이타 흐름을 처리하기 위해 스트림사용하기.

Written by Chris Buckett

Whether running in the browser as part of the various HTML events, such as button.onClick, or on the server as part of the dart:io changes, Streams form a unified interface to anything that might send out a repeating series of data.

버튼 onclick과같은 다양한 HTML 이벤트들이 브라우저에서 실행하는 것이나, dart:io 를 사용해서 서버에서 변화를 준다거나 할때, Stream은 연속된 데이타를 반복해서 보내는 것에 대해서 통합된 인터페이스를 제공합니다.

This article explains how to consume streams using this unified interface.

이번 문서는 통합된 인터페이스를 사용해서 어떻게 스트림을 사용하는지 설명할 것입니다.

Background reading: futures
future을 사용해서 백그라운드에서 읽기

Before we get started, it is important to note that Streams are part of the dart:async library. They share a close relationship with Dart’s async staple, Future, due to their asynchronous nature. (You can’t block code until a user clicks a button!)

시작하기에 앞서, Streams는 dart:async 라이브러리에 포함되어 있다는 것을 알아두는 것은 중요하다. 그것들은 비동기 유형을 위한 Dart의 중요한 Future와 밀접한 관계가 있다. (당신은 사용자가 버튼을 클릭할때까지 코드를 블락시킬 필요가 없다)

For details about using Futures, refer to the previous tutorial Use Future-Based APIs.

Future 사용에 대한 더 자세한 것을 알고 싶으면 앞에 있는 튜토리얼인 Use Future-Based APIs을 참고하세요.

What are streams for?
stream은 무엇인가?

Imagine you are writing a chat application. On a single client, you will be receiving messages and displaying them to the user. You can’t simply write a while loop because that will block execution, so you need to use async callbacks. This is an ideal use case for streams: you have one part of your code pushing data into the stream, and another part of your code listening to the stream.

채팅 프로그램을 개발하고 있다고 상상해 보세요. 단일 클라이언트에서, 당신은 메시지를 받을 것이고 다른 유저로 부터 받은 그 메시지를 보여줄 것입니다. 당신은 반목문을 도는 동안 쉡게 개발할 수 없습니다. 왜냐하면 그 반복문이 실행을 블락할 것이기 때문입니다. 그래서 당신은 비동기 콜백을 사용할 필요가 있습니다. 이것은 스트림을 사용해야 하는 이상적인 케이스 입니다. 코드의 한 부분은 stream에 데이터를 밀어 넣는 것이고 다른 부분은 스트림에서 데이터를 받는 부분입니다.

Key concepts
주요 개념

  • Consuming a stream: Data is sent out of a stream to a StreamSubscriber (or possibly multiple subscribers).

  • 스트림  꺼내쓰기: 데이터는 스트림을 StreamSubscriber 에 보냅니다. (또는 가능하다면 여러개의 subscriber)

  • Populating a stream: Data gets into a stream from a StreamController.

  • 스트림 채우기: streamcotroller을 통해서 stream에서 데이터를 얻을 수 있다.

We’ll look at the consuming a stream in this article as you’re more likely to come across streams as a consumer from existing APIs within Dart. Populating a stream will be covered in a future article.

우리는  이번 문서에서 Dart안에 있는 API를 사용해서 소비자로써 좀 더 스트림을 이해할 수 있도록 스트림 꺼내 쓰기를 살펴 볼 것 입니다.스트림 채우기는 좀 더 나중에 알아보겠습니다.

Consuming a stream
스트림 꺼내 쓰기

Let’s take a look at some simple stream code. For simplicity we’re going to create a stream from a fixed, literal list at the moment by using the stream’s fromIterable() constructor, rather than by dynamically populating it with a StreamController.

간단한 스트림 코드를 한 번 살펴봅시다. 단순하게 하기 위해서, 우리는 StreamController를 사용하여 스트림을 동적으로 채우기 보다는 스트림의 fromIterable()생성자를 사용하여 고정된 문자 리스트에서 스트림을 만들어 보겠습니다.


var data = [1,2,3,4,5]; // some sample data
var stream = new Stream.fromIterable(data); // create the stream

Now that we have a stream that is ready to send out some data, we can use that stream to listen to some data.

자 이제, 데이터를 바깥으로 보낼 준비가 된 스트림이 있습니다, 그리고 데이터를 받게될 스트림도 사용할 수 있습니다.

The typical way is to use the stream’s listen() method to subscribe to the stream. This has a number of optional parameters, and one mandatory parameter, which is the onData handler callback function:

이 전통적인 방법은 그 스트림에 등록하기 위해서 해당 스트림의 listen() 메소드를 사용하는 것입니다. 이 메소드에는 몇 개의 부가적인 파라미터와 1개의 필수 파라미터가 있습니다. 필수 파라미터는 onData 핸들러 콜백 함수 입니다.

import 'dart:async';

main
() {
 
var data = [1,2,3,4,5]; // some sample data
 
var stream = new Stream.fromIterable(data);  // create the stream

 
// subscribe to the streams events
 stream
.listen((value) {       //
   print
("Received: $value");  // onData handler
 
});                           //
}

The listen() method is fired every time some data is received. In in our stream, the listen()callback is called for each of the data elements, so the output of running this code is as expected:

listen() 메소드는 데이터를 받을때 마다 실행됩니다. 위의 스트림에서, listen() 콜백은 데이터 요소 각각에 의해 호출됩니다. 그래서 이 코드의 실행 출력 결과는 아래와 같습니다.

Received: 1
Received: 2
Received: 3
Received: 4
Received: 5

There are other ways to consume data from the stream, using properties such as first, last,length, and isEmpty. Each of these properties returns a future. For example:

스트림에 데이터를 꺼내어 쓰는 방법에는, first, last, length 같은 프로터티 같은 것을 사용하는 다른 방법이 있습니다. 이 프로퍼티들은 future를 리턴합니다.

stream = new Stream.fromIterable([1,2,3,4,5]);
stream
.first.then((value) => print("stream.first: $value"));  // 1

stream
= new Stream.fromIterable([1,2,3,4,5]);
stream
.last.then((value) => print("stream.last: $value"));  // 5  

stream
= new Stream.fromIterable([1,2,3,4,5]);
stream
.isEmpty.then((value) => print("stream.isEmpty: $value"));  // false

stream
= new Stream.fromIterable([1,2,3,4,5]);
stream
.length.then((value) => print("stream.length: $value"));  // 5

Streams comes in two flavours: single or multiple (also known as broadcast) subscriber. By default, our stream is a single-subscriber stream. This means that if you try to listen to the stream more than once, you will get an exception, and using any of the callback functions or future properties counts as listening.

스트림은 2가지 종류가 있습니다. 싱글과 멀티(브로드 캐스트로 알려진) 이용 방식이 있습니다. 기본적으로, 예제에 있는 스트림은 싱글 형식의 스트림입니다. 이것은 한 번 이상 스트림을 받을려고 시도 한다면 예외가 발생하고 , 콜백함수 사용이나 future 프로퍼티를 사용하는 것도 스트림을 받는 것으로 간주됩니다.

You can convert the single-subscriber stream into a broadcast stream by using the asBroadcastStream() method, as shown below:

asBroadcastStream()을 사용해서 싱글 형식의 스트림을 브로드캐스트 스트림으로 바꿀 수 있습니다.

var data = [1,2,3,4,5];
var stream = new Stream.fromIterable(data);
var broadcastStream = stream.asBroadcastStream();

broadcastStream
.listen((value) => print("stream.listen: $value"));
broadcastStream
.first.then((value) => print("stream.first: $value")); // 1
broadcastStream
.last.then((value) => print("stream.last: $value")); // 5
broadcastStream
.isEmpty.then((value) => print("stream.isEmpty: $value")); // false
broadcastStream
.length.then((value) => print("stream.length: $value")); // 5

Now that the stream allows multiple subscribers, you can add multiple listeners. You can check whether a stream is a broadcast stream by checking the stream.isBroadcast property.

이제는 스트림이 멀티 형식의 스트림을 사용가능해졌기 때문에, 멀티 리스너를 추가할 수 있습니다. 당신은 stream.isBroadcast 프로퍼티를 체크해서 스트림이 브로드채스트 스트림인지 아닌지 알 수 있습니다.

Common Stream methods
공통 스트림 메소드

Lots of methods are available on the Stream class. In the following section, I’ll describe some of the more common ones. Be sure to check out the API docs for the complete list.

Stream 클래스에는 많은 메소드가 있습니다. 다음의 섹션에서, 몇 개의 공통 메소드에 대해서 설명할 것입니다. 모든 메소드 리스트를 보고 싶다면 API docs를 체크하세요.

Subsets of stream data
스트림 데이터의 서브셋

Streams have some useful methods for extracting parts of the data being sent out from the stream. The take(), skip(), takeWhile(), skipWhile(), and where() methods allow you to take a subset of data, as shown by the following example. Each outputs its own stream that you can listen to.
Stream은 스트림으로 부터 받은 데이터 부분을 추출하는 유용한 메소드가 있습니다. take() 그리고
take(), skip(), takeWhile(), skipWhile()where()메소드는 당신이 데이터의 서브셋을 추출할수 있게 하는 메소드들 입니다. 아래에 그것에 대한 예제가 있습니다. 각각의 아웃풋들은 당신이 받을 수 있는 자신만의 스트림입니다.

broadcastStream
   
.where((value) => value % 2 == 0) // divisible by 2
   
.listen((value) => print("where: $value")); // where: 2
                                               
// where: 4

broadcastStream
   
.take(3) // takes only the first three elements
   
.listen((value) => print("take: $value")); // take: 1
                                              
// take: 2
                                              
// take: 3

broadcastStream
   
.skip(3)  // skips the first three elements
   
.listen((value) => print("skip: $value")); // skip: 4
                                              
// skip: 5

broadcastStream
   
.takeWhile((value) => value < 3) // take while true
   
.listen((value) => print("takeWhile: $value")); // takeWhile: 1
                                                   
// takeWhile: 2

broadcastStream
   
.skipWhile((value) => value < 3) // skip while true
   
.listen((value) => print("skipWhile: $value")); // skipWhile: 4
                                                   
// skipWhile: 5

Transforming stream data
스트림 데이터 변환하기

Another useful method is the transform() method, which takes a StreamTransformer instance. This allows you to modify the contents of the stream. The StreamTransformer constructor takes a handleData function, which is called for each value passed from the stream. You can modify the value as you wish, and add it back to the StreamSink, which results in the modified values being output on the transform() method’s own stream. The example below takes our data [1,2,3,4,5]and converts each item into two new String values, "Message n" and "Body n". Each string is placed onto the new stream.

다른 유용한 메소드는 transform() 메소드입니다. 이 메소드는 StreamTransformer 클래스의 인스턴스입니다. 이것은 당신이 스트림의 데이터를 수정할 수 있도록 합니다. 이 StreamTransformer 생성자는 스트림에서 전달된 값에 의해 호출되는 handleData 함수를 가집니다. 이것으로 당신이 원하는대로 값을 수정할수 있고 StreamSink에 다시 그 값을 추가할 수 있습니다. StreamSinktransform() 메소드 자신의 스트림에 출력되는 변경된 값에서 발생합니다. 아래에 있는 예제는 [1,2,3,4,5] 를 가지고  "Message n"  과  "Body n". 이라고 하는 2개의 새로운 스트링 값으로 각 항목을 변환합니다. 각 문자열은 새로운 스트림에 배치됩니다.

(아래 예제에서  new StreamTransformer(handleData: (value, sink)  이 부분이 SDK 1.2 에서 에러가 나네요..더 이상 지원하지 않는 것 같습니다.  대신 new StreamTransformer.fromHandlers(handleData: (value, sink)) 를 사용하세요.)

// define a stream transformer
var transformer = new StreamTransformer(handleData: (value, sink) {
 
// create two new values from the original value
 sink
.add("Message: $value");
 sink
.add("Body: $value");
});
 
// transform the stream and listen to its output
stream
.transform(transformer).listen((value) => print("listen: $value"));

Running this produces the following output:

이 케드의 실행결과는 아래와 같습니다.

listen: Message: 1
listen: Body: 1
listen: Message: 2
listen: Body: 2
listen: Message: 3
listen: Body: 3
listen: Message: 4
listen: Body: 4
listen: Message: 5
listen: Body: 5

Perhaps the most common transform you will use is transforming a List into a String by using a `UTF8.decoder` from `dart:convert`, such as when reading data from a file or HTTP request, as in the following example.

아마도  당신이 사용할 가장 일반적인 변환방식은  ‘dart:convert’ 에서 ‘UTF8.decoder’를 사용해서 문자열 목록을 변환하는 것입니다.  예를 들면, 파일이나 HTTP request에서 데이터를 읽을때입니다. 아래에 있는 예제처럼 말이죠.

File file = new File("some_file.txt");
file
.openRead()
   
.transform(UTF8.decoder) // use a UTF8.decoder
   
.listen((String data) => print(data), // output the data
       onError
: (error) => print("Error, could not open file"),
       onDone
: () => print("Finished reading data"));

Validating stream data
스트림 데이터 유효성 검사

Sometimes, you want to validate that the data returned from a stream meets certain conditions. A following functions return Future<bool> values: any(), every(), and contains().

때때로 당신은 스트림에서 리턴받은 데이터를 어떤 상태에서 유효성 검사를 하기를 원합니다. 아래의 함수는 Future<bool> 값을 반환합니다. any(), every()contains() 3가지 함수입니다.

broadcastStream
   
.any((value) => value < 5)
   
.then((result) => print("Any less than 5?: $result")); // true
 
broadcastStream
   
.every((value) => value < 5)
   
.then((result) => print("All less than 5?: $result")); // false
 
broadcastStream
   
.contains(4)
   
.then((result) => print("Contains 4?: $result")); // true

Single value streams
단일 값 스트림

Some streams are designed to return only a single value, and you want to ensure that you only retrieve a single value from them. The single getter and singleWhere() method both return a future containing the single value, or raise an error if they don’t. For example, with our data set containing 5 values: [1,2,3,4,5], the following will return the value 1:

일 스트림은 단일 값을 리턴하도록 디자인 되었습니다. 그리고 당신은 스트림으로부터 단일 값만을 받기를 확실히 하고 싶어합니다. 이 single 게터와 singleWhere() 메소드 는 단일 값을 포함하는 future를 리턴하거나 그렇지 않은 경우 에러를 발생시킵니다. 예를 들어서,[1,2,3,4,5]라고 하는 5개의 값을 가지고 있는 데이터 셋은 아래 예제에서 값 1을 리턴할 것입니다.

broadcastStream
   
.singleWhere((value) => value < 2)  // there is only one value less than 2
   
.then((value) => print("single value: $value"));
   
// outputs: single value: 1

However, the following raises an error and halts the application (because the error is unhandled):

그러나, 아래의 예제는 에러를 발생시키고 에러를 처리해야 하기 때문에 어플리케이션을 중단시킵니다.

broadcastStream
   
.single  // will fail - there is more than one value in the stream
   
.then((value) => print("single value: $value"));

This brings us neatly on to…
이것은 깔끔하게 우리의 실력을 향상시킵니다..

Error handling in streams and futures
스트림과 future에서 에러 처리하기.

There is already an excellent article about handling errors with future based APIs, so I’ll not repeat that here. It’s useful to note, though, that we can rewrite our previous snippet to include some error handling so that we can detect that the single call has failed. A Future’s then()function returns a future, and you can use its catchError() handler. This catchError handler will catch any errors thrown within the then() callback:

“future 기반 API 에러 처리에 대한 휼륭한 기사"가 이미 있기 때문에 여기에서는 반복하지 않을 것입니다. 우리가 single 호출이 실패되었음을 감지할 수 있도록 몇 가지 에러 처리를 포함하는 앞의 코드를 다시 작성할 수 있기 하기 위해서 읽어보시면 아주  유용한 기사입니다. Future의 then() 함수는 future 를 반환하고 future 의 catchError() 처리기를 사용할 수 있습니다. catchError  처리기는  then() 콜백 내에서 발생된 어떤 에러도 잡아낼 것입니다.

broadcastStream
   
.single  // will fail - there is more than one value in the stream
   
.then((value) => print("single value: $value"))
   
.catchError((err) => print("Expected Error: $err")); // catch any error in the then()
   
// output: Bad State: More than one element

Error handling with StreamSubscription
StreamSubscription 오류 처리하기

When you use the listen() function to listen to values coming from a stream, you have the option of adding error handling. The listen function creates a StreamSubscription instance, which is the return value of the listen() function.

스트림에서 넘어오는 값을 받기 위해서 listen() 함수를 사용할때 당신은 에러 처리기를 추가할 수 있는 옵션이 있습니다. listen 함수는 listen()  함수의 리턴값인  StreamSubscription 인스턴스를 생성합니다.

A StreamSubscription has a number of handlers, namely: onData, onError and onDone. Each of these can be assigned via the listen() function, or later, via the returned StreamSubscription object. Note the onError handler, which you can use to catch errors output from the stream:

streamSubscription 은  onData, onError 그리고 onDone 이라고 하는 몇 개의 처리기를 가지고 있습니다. 이러한 각각의 처리기는 listen() 함수를 통해 할당되어지거나 또는 나중에 리턴된 StreamSubscription 객체에 의해서 할당되어집니다. 스트림에서 오류를 잡기 위해 사용할 수 있는 onError 처리기를 참고 하세요.

// setup the handlers through the subscription's handler methods
var subscription = stream.listen(null);
subscription
.onData((value) => print("listen: $value"));
subscription
.onError((err) => print("error: $err"));
subscription
.onDone(() => print("done"));

and:

// setup the handlers as arguments to the listen() function
var subscription = stream.listen(
   
(value) => print("listen: $value"),
   onError
: (err) => print("error: $err"),
   onDone
: () => print("done"));

These two both print the same output:

listen: 1
listen: 2
listen: 3
listen: 4
listen: 5
done

One of the benefits of using the form var subscription = stream.listen(null) and then setting up the onData handler separately means that you can use the subscription object in the data handler itself.

var subscription = stream.listen(null) 이것을 사용하고 나서 분리해서 onData 핸들러(처리기)를 설정하는 방식의 좋은 점중의 하나는 데이터 핸들러 자체에 subscription 객체를 사용할 수 있다는 것이다.

The onDone handler is called when there is no more data, and the underlying stream is closed.

onDone 핸들러는 더 이상 데이터가 없을때 불리어지고 그 이후에 스트림은 닫힙니다.

Unsubscribing from a stream
스트림에서 취소하기.

You can use the StreamSubscription object to unsubscribe from the stream, using the cancel()method. For example, the listener in the following code unsubscribes from the stream after receiving the value 2, so it never receives the onDone message:

스트림에서 취소하기 위해서 StreamSubscription 객체를 사용할 수 있습니다.이 때 cancel() 메소드를 사용합니다. 예를 들어, 아래 코드의 리스너는 2라는 값을 받은 후에 스트림을 취소합니다. 그래서 onDone 에서 메세지를 절대 받을 수가 없습니다.

var subscription = stream.listen(null);
subscription
.onData((value) {
 print
("listen: $value");
 
if (value == 2) subscription.cancel(); // cancel the subscription
});
subscription
.onError((err) => print("error: $err"));
subscription
.onDone(() => print("done"));

Streams are generic
스트림은 제너릭이다.

All the stream classes are also generic, which means that you get strongly typed data in the handlers. For example, if you create a Stream<String>, then all the handler functions will also be expecting a String, as shown by the following code:

모든 스트림 클래스는 또한, 핸들러에서 엄격하게 타입이 지정된 데이터를 얻는 것을 의미하는 제너릭입니다. 예를들면, 당신이 Stream<String> 을 만들경우에, 아래의 코드에 보이는 것처럼 모든 핸들러 함수 또한 String를 기대할 것입니다.

var data = [1,2,3,4,5]; // ints, valid
// var data = ["1","2","3","4","5"]; // strings, not valid
var stream = new Stream<int>.fromIterable(data); // Stream<int>
stream
.listen((value) { // value must be an int
 print
("listen: $value");
});

Some real world examples of consuming a stream
스트림 꺼내 쓰기의 실제 예.

Now that you’ve seen how to consume data in a stream, let’s take a look at a couple of real-world examples: handling button clicks, and reading data from a file.

이제 당신은 스트림에서 어떻게 데이터를 꺼내 쓰는지를 살펴보았으므로, 이제 실제 예를 살펴 봅시다. 버튼 클릭을 핸들링하고 파일에서 데이터를 읽어보겠습니다.

Button clicks in dart:html
dart:html에서 버튼 클릭

Buttons have a number of onSomeEvent streams defined, and the onClick stream is defined as Stream<MouseEvent>. This type means that the data that you receive when you listen to the onClick stream is all going to be MouseEvents.

버튼은 많은 정의된 onSomeEvent 스트림을 가지고 있고 그 중에 onClick 스트림은 Stream<MouseEvent> 로 정의되어 집니다. 이 타입은 onClick 스트림에서 받은 데이터는 모두 MouseEvent가 된다라는 의미입니다.

The following code sets up a button and a couple of event handlers. One event handler remains registered, and the other unregisters itself after the third button click.

아래의 코드는 버튼과 이벤트 핸들러의 커플을 설치합니다. 첫번째 이벤트 핸들러는 등록되어져있고, 다른 이벤트 핸들러는 3번째 버튼 클릭후에 그 자신을 등록 해제합니다.

import 'dart:html';

void main() {
 
var button = new ButtonElement();
 document
.body.children.add(button);
 
 button
.text = "Foo";
 
var clickCount = 0;
 button
.onClick.listen((mouseEvent) {
   print
("clicked"); // remain subscribed for all clicks
 
});
 
 
var subscription = button.onClick.listen(null);
 subscription
.onData((mouseEvent) {
   print
("copy that");
   clickCount
++;
   window
.alert("Clicked");
   
if (clickCount == 3) {
     subscription
.cancel(); // unsubscribe after the third click
   
}
 
});  
}

When the button is clicked, the click counter is incremented. On the third click, the second event handler unsubscribes itself.

버튼이 클릭되었을때, 클릭 카운터는 증가합니다. 3번째 클릭을 했을때는 두번째 이벤트 핸들러가 등록취소 됩니다.

Reading a file in dart:io
dart:io 로 파일 읽기

The second real-world example shows how to read some data from a file on the filesystem. The file.openRead() returns a stream containing the file’s contents. The stream (which contains a List<int>) is decoded using a UTF8.decoder class from dart:convert to allow for UTF-8 conversion.

두번째 실제 예는 어떻게 파일시스템에서 파일을 읽는지 보여주는 것입니다. file.openRead() 는 파일 내용을 포함하는 스트림을 리턴합니다. 이 스트림은( List<int> 를 가지고 있는) UTF-8로 변환이 가능하게 하기 위해서 dart:convert 에서 UTF8.decoder를 이용해서 해독됩니다.

import 'dart:io';

main
() {
 File file
= new File("some_file.txt");
 file
.openRead()
     
.transform(UTF8.decoder) // use a UTF8.decoder
     
.listen((String data) => print(data), // output the data
       onError
: (error) => print("Error, could not open file"),
       onDone
: () => print("Finished reading data"));
}

Conclusion
결론

Streams are unified across Dart’s asynchronous APIs, providing a powerful way to deal with streams of data, whether that data is event objects, bytes, or your own custom classes.

스트림은, 데이터가 이벤트 객체이든지 바이트 이든지 아니면 사용자가 만든 클래스여도 상관없이,  데이터 스트림을 처리하기 위해 강력한 방법을 제공하는 Dart의 비동기 API를 통해서 통합됩니다.

About the author

Chris Buckett is a Technical Manager for Entity Group Ltd, responsible for building and delivering enterprise client-server webapps, mostly with GWT, Java and .Net. He runs the dartwatch.com blog, and has written the book Dart in Action, which is available at manning.com.


300x250

'개발 > dart' 카테고리의 다른 글

10. Get Input from a Fom  (0) 2014.03.12
9. Fetch Data Dynamically  (0) 2014.03.04
7. Use Future-Based APIs  (0) 2014.03.02
6. Defined a Custom Element  (0) 2014.03.02
5. Install Shared Packages  (0) 2014.03.02

댓글