binary draft

This commit is contained in:
Ilya Kantor 2019-03-17 20:05:02 +03:00
parent 7f9a1e2c7a
commit 973f97cc09
38 changed files with 906 additions and 312 deletions

View file

@ -69,8 +69,9 @@ new Promise(function(resolve, reject) {
The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value. The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value.
Please note: technically we can also add many `.then` to a single promise. This is not chaining: **A classic newbie error: technically we can also add many `.then` to a single promise. This is not chaining.**
For example:
```js run ```js run
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); setTimeout(() => resolve(1), 1000);
@ -92,7 +93,7 @@ promise.then(function(result) {
}); });
``` ```
What we did here is just several handlers to one promise. They don't pass the result to each other, instead they process it idependantly. What we did here is just several handlers to one promise. They don't pass the result to each other, instead they process it independently.
Here's the picture (compare it with the chaining above): Here's the picture (compare it with the chaining above):

View file

@ -3,7 +3,7 @@
Promise handlers `.then`/`.catch`/`.finally` are always asynchronous. Promise handlers `.then`/`.catch`/`.finally` are always asynchronous.
Even when a Promise is immediately resolved, the code on the lines *below* your `.then`/`.catch`/`.finally` may still execute first. Even when a Promise is immediately resolved, the code on the lines *below* your `.then`/`.catch`/`.finally` will still execute first.
Here's the code that demonstrates it: Here's the code that demonstrates it:
@ -23,11 +23,11 @@ Why `.then` triggered after? What's going on?
# Microtasks # Microtasks
Asynchronous tasks need proper management. For that, the standard specifies an internal queue `PromiseJobs`, more often called the "microtask queue" (v8 term). Asynchronous tasks need proper management. For that, the standard specifies an internal queue `PromiseJobs`, more often referred to as "microtask queue" (v8 term).
As said in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues): As said in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues):
- The queue is first-in-first-out: tasks that get enqueued first are run first. - The queue is first-in-first-out: tasks enqueued first are run first.
- Execution of a task is initiated only when nothing else is running. - Execution of a task is initiated only when nothing else is running.
Or, to say that simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue. They are not executed yet. Javascript engine takes a task from the queue and executes it, when it becomes free from the current code. Or, to say that simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue. They are not executed yet. Javascript engine takes a task from the queue and executes it, when it becomes free from the current code.
@ -58,7 +58,7 @@ Now the order is as intended.
Browser Javascript, as well as Node.js, is based on an *event loop*. Browser Javascript, as well as Node.js, is based on an *event loop*.
"Event loop" is a process when the engine sleeps waits for events. "Event loop" is a process when the engine sleeps and waits for events, then reacts on those and sleeps again.
Examples of events: Examples of events:
- `mousemove`, a user moved their mouse. - `mousemove`, a user moved their mouse.
@ -71,9 +71,11 @@ Things happen -- the engine handles them -- and waits for more to happen (while
![](eventLoop.png) ![](eventLoop.png)
When an event happens, and the engine is busy, it gets into a so-called "macrotask queue" (v8 term). As you can see, there's also a queue here. A so-called "macrotask queue" (v8 term).
For instance, while the engine is busy processing a network `fetch`, a user may move their mouse causing `mousemove`, and `setTimeout` is due and so on, just as painted on the picture above. When an event happens, and the engine is busy, the event is enqueued.
For instance, while the engine is busy processing a network `fetch`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, just as painted on the picture above.
Events from the macrotask queue are processed on "first came first served" basis. When the engine browser finishes with `fetch`, it handles `mousemove` event, then `setTimeout` handler, and so on. Events from the macrotask queue are processed on "first came first served" basis. When the engine browser finishes with `fetch`, it handles `mousemove` event, then `setTimeout` handler, and so on.
@ -120,20 +122,24 @@ Promise.resolved()
Naturally, `promise` shows up first, because `setTimeout` macrotask awaits in the less-priority macrotask queue. Naturally, `promise` shows up first, because `setTimeout` macrotask awaits in the less-priority macrotask queue.
**As a side effect, macrotasks are handled only when promises give the engine a "free time".**
So call have a promise chain that doesn't wait for anything, then things like `setTimeout` or event handlers can never get in the middle.
## Summary ## Summary
Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (v8 term). - Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (v8 term).
**So, `.then/catch/finally` is called after the current code is finished.** **So, `.then/catch/finally` is called after the current code is finished.**
If we need to guarantee that a piece of code is executed after `.then/catch/finally`, it's best to add it into a chained `.then` call. If we need to guarantee that a piece of code is executed after `.then/catch/finally`, it's best to add it into a chained `.then` call.
There's also a "macrotask queue" that keeps various events, network operation results, `setTimeout`-scheduled calls, and so on. These are also called "macrotasks" (v8 term). - There's also a "macrotask queue" that keeps various events, network operation results, `setTimeout`-scheduled calls, and so on. These are also called "macrotasks" (v8 term).
The engine uses the macrotask queue to handle them in the appearance order. The engine uses the macrotask queue to handle them in the appearance order.
**Macrotasks run after the code is finished *and* after the microtask queue is empty.** **Macrotasks run after the code is finished *and* after the microtask queue is empty.**
In other words, they have lower priority. In other words, they have lower priority.
So the order is: regular code, then promise handling, then everything else. So the order is: regular code, then promise handling, then everything else, like events etc.

View file

@ -307,7 +307,7 @@ async function f() {
})(); })();
``` ```
There's no ambiguity here: `await` always finishes first. There's no ambiguity here: `await` always finishes first, because (as a microtask) it has a higher priority than `setTimeout` handling.
## Summary ## Summary

View file

@ -7,7 +7,7 @@ libs:
IndexedDB is a built-in database, much more powerful than `localStorage`. IndexedDB is a built-in database, much more powerful than `localStorage`.
- Also key/value storage, but not only strings: value can be (almost) anything, multiple key types. - Key/value storage not only strings: value can be (almost) anything, multiple key types.
- Supports transactions for reliability. - Supports transactions for reliability.
- Supports key range queries, indexes. - Supports key range queries, indexes.
- Can store much more data than `localStorage`. - Can store much more data than `localStorage`.
@ -16,7 +16,7 @@ That power is usually excessive for traditional client-server apps. IndexedDB is
The native interface to IndexedDB, described in the specification <https://www.w3.org/TR/IndexedDB>, is event-based. The native interface to IndexedDB, described in the specification <https://www.w3.org/TR/IndexedDB>, is event-based.
We can also use `async/await`-based interface with the help of a promise-based wrapper, like <https://github.com/jakearchibald/idb>. That's really convenient, but the wrapper is not perfect, it can't replace events for all cases, so we'll start with events, and then use the wrapper. We can also use `async/await` with the help of a promise-based wrapper, like <https://github.com/jakearchibald/idb>. That's pretty convenient, but the wrapper is not perfect, it can't replace events for all cases, so we'll start with events, and then use the wrapper.
## Open database ## Open database

View file

@ -0,0 +1,18 @@
function concat(arrays) {
// sum of individual array lengths
let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
if (!arrays.length) return null;
let result = new Uint8Array(totalLength);
// for each array - copy it over result
// next array is copied right after the previous one
let length = 0;
for(let array of arrays) {
result.set(array, length);
length += array.length;
}
return result;
}

View file

@ -0,0 +1,13 @@
function concat(arrays) {
// ...your code...
}
let chunks = [
new Uint8Array([0, 1, 2]),
new Uint8Array([3, 4, 5]),
new Uint8Array([6, 7, 8])
];
console.log(Array.from(concat(chunks))); // 0, 1, 2, 3, 4, 5, 6, 7, 8
console.log(concat(chunks).constructor.name); // Uint8Array

View file

@ -0,0 +1,31 @@
describe("concat", function() {
let chunks = [
new Uint8Array([0, 1, 2]),
new Uint8Array([3, 4, 5]),
new Uint8Array([6, 7, 8])
];
it("result has the same array type", function() {
let result = concat(chunks);
assert.equal(result.constructor, Uint8Array);
});
it("concatenates arrays", function() {
let result = concat(chunks);
assert.deepEqual(result, new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]));
});
it("returns empty array on empty input", function() {
let result = concat([]);
assert.equal(result.length, 0);
});
});

View file

@ -0,0 +1,4 @@
# Concatenate typed arrays
Given an array of `Uint8Array`, write a function `concat(arrays)` that returns a concatenation of them into a single array.

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before After
Before After

View file

@ -0,0 +1,261 @@
# ArrayBuffer and views
Binary data appears when we work with arbitrary files (uploading, downloading, creation). Or when we want to do image/audio processing.
That's all possible in Javascript, and binary operations are high-performant.
Although, there's a bit of confusion, because there are many classes. To name a few:
- `ArrayBuffer`, `Uint8Array`, `DataView`, etc.
It may seem complex, but in fact it's not. Everything's fairly simple.
Let's sort things out.
**The basic binary object is `ArrayBuffer` -- a fixed-length raw sequence of bytes.**
We create it like this:
```js run
let buffer = new ArrayBuffer(16); // create a buffer of length 16
alert(buffer.byteLength); // 16
```
This allocates a contiguous memory area of 16 bytes and pre-fills it with zeroes.
```warn header="`ArrayBuffer` is not `Array` at all"
`ArrayBuffer` has nothing in common with `Array`:
- It has a fixed length, we can't increase or decrease it.
- It takes exactly that much space in the memory.
- It can't store any value: only bytes, the exact number of them.
```
`ArrayBuffer` is a raw sequence of bytes. What's stored in it? It has no clue.
**To manipulate an `ArrayBuffer`, we need to use a "view" object.**
A view object does not store anything on it's own. It's just an "eyeglasses" that gives an interpretation of the bytes stored in the `ArrayBuffer`.
For instance:
- **`Uint8Array`** -- treats each byte as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so can hold only that much). That's called a "8-bit unsigned integer".
- **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer".
- **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer".
- **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from <code>5.0x10<sup>-324</sup></code> to <code>1.8x10<sup>308</sup></code>.
So, the binary data in an `ArrayBuffer` of 16 bytes can be interpreted as 16 "tiny numbers", or 8 bigger numbers, or 4 even bigger, or 2 floating-point values with high precision.
![](arraybuffer-views.png)
`ArrayBuffer` is the core object, the root of everything, the raw binary data. But if we're going to write into it, or iterate over it, basically for almost any operation we must use a view, e.g:
```js run
let buffer = new ArrayBuffer(16); // create a buffer of length 16
*!*
let view = new Uint32Array(buffer); // treat every 4 bytes as an integer number
*/!*
alert(view.length); // 4, it stores that many numbers
alert(view.byteLength); // 16, the length in bytes
// let's write a value
view[0] = 123456;
// iterate over bytes
for(let num of view) {
alert(num); // 123456, then 0, 0, 0 (4 numbers total)
}
alert(Uint32Array.BYTES_PER_ELEMENT); // 4 (that many bytes per number)
```
The common term for all these views is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities.
They are much more like regular arrays: have indexes and iterable.
## TypedArray
A typed array constructor (be it `Int8Array` or `Float64Array`, doesn't matter) behaves differently depending on argument types.
There are 5 variants:
```js
new TypedArray(buffer, [byteOffset], [length]);
new TypedArray(object);
new TypedArray(typedArray);
new TypedArray(length);
new TypedArray();
```
A view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these calls except the first one.
1. If an `ArrayBuffer` argument is supplied, the view is created over it. We used that syntax already.
Optionally we can provide `byteOffset` to start from (0 by default) and the `length` (till the end of the buffer by default).
2. If an `Array`, or any array-like object is given, it behaves as `Array.from`: creates a typed array of the same length and copies the content.
We can use it to pre-fill the array with the data:
```js run
*!*
let arr = new Uint8Array([0, 1, 2, 3]);
*/!*
alert( arr.length ); // 4
alert( arr[1] ); // 1
```
3. If another `TypedArray` is supplied, it's contents is also copied. Values are converted to the new type in the process. The new array will have the same length.
```js run
let arr16 = new Uint16Array([1, 1000]);
*!*
let arr8 = new Uint8Array(arr16);
*/!*
alert( arr8[0] ); // 1
alert( arr8[1] ); // 232 (tried to copy 1000, but can't fit 1000 into 8 bits)
```
4. For a numeric argument `length` -- creates an `ArrayBuffer` to contain that many elements. Its byte length will be `length` multiplied by the number of bytes in a single item `TypedArray.BYTES_PER_ELEMENT`:
```js run
let arr = new Uint16Array(4);
alert( Uint16Array.BYTES_PER_ELEMENT ); // 2 bytes per number
alert( arr.byteLength ); // 8 bytes in the buffer, to contain four 2-byte numbers
```
5. Without arguments, creates an zero-length empty `ArrayBuffer`.
Here's the full list of typed arrays:
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment (see below).
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
```warn header="No `int8` or similar single-valued types"
Please note, despite of the names like `Int8Array`, there's no type like `int8`, or similar in Javascript.
That's logical, as `Int8Array` is technically not an array, but rather a view on `ArrayBuffer`.
```
### Out-of-bounds behavior
What if we attempt to write an out-of-bounds value into a typed array? There will be no error. But the result may be not what we want.
For instance, let's try to put 256 into `Uint8Array`. In binary form, 256 is `100000000` (9 bits), but `Uint8Array` only provides 8 bits per value, that makes the available range from 0 to 255.
For bigger numbers, only the rightmost (less significant) 8 bits are stored, and the rest is cut off:
![](8bit-integer-256.png)
So we'll get zero.
For 257, the binary form is `100000001` (9 bits), the rightmost 8 get stored, so we'll have `1` in the array:
![](8bit-integer-257.png)
In other words, the number modulo 2<sup>8</sup> is saved.
Here's the demo:
```js run
let buffer = new ArrayBuffer(16);
let uint8array = new Uint8Array(buffer);
let num = 256;
alert(num.toString(2)); // 100000000 (binary representation)
uint8array[0] = 256;
uint8array[1] = 257;
alert(uint8array[0]); // 0
alert(uint8array[1]); // 1
```
`Uint8ClampedArray` is special in this aspect, its behavior is different. It saves 255 for any number that is greater than 255, and 0 for any negative number. That behavior is sometimes useful for image processing.
## TypedArray methods
`TypedArray` has regular `Array` methods, with notable exceptions.
We can iterate, `map`, `slice`, `find`, `reduce` etc.
There are few things we can't do though:
- No `splice` -- we can't "delete" a value, because typed arrays are views on a buffer, and these are fixed, contiguous areas of memory. All we can do is to assign a zero.
- No `concat` method.
There are two additional properties:
- `arr.buffer` -- the reference to the underlying `ArrayBuffer`.
- `arr.byteLength` -- the byte size of the underlying `ArrayBuffer`.
...And methods:
- `arr.set(fromArr, [offset])` copies all elements from `fromArr` to the `arr`, starting at position `offset` (0 by default).
- `arr.subarray([begin, end])` creates a new view of the same type from `begin` to `end` (exclusive). That's similar to `arr.slice` method, but doesn't copy anything -- just creates a new view.
These methods allow us to copy typed arrays one onto another, create new arrays from existing ones.
## DataView
[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`.
It allows to access the data on any offset in any format.
- For typed arrays, the constructor dictates what the format is, then we access i-th number as `arr[i]`. All array members are assumed to have the same format.
- With `DataView` we access the data with methods like `.getUint8(i)`. So we decide what the format is at method call time instead of the construction time.
The syntax:
```js
new DataView(buffer, [byteOffset], [byteLength])
```
- **`buffer`** -- the underlying `ArrayBuffer`. Unlike typed arrays, `DataView` doesn't create a buffer on its own. We need to have it ready.
- **`byteOffset`** -- the starting byte position of the view (by default 0).
- **`byteLength`** -- the byte length of the view (by default till the end of `buffer`).
For instance, here we extract numbers in different formats from the same buffer:
```js run
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);
// get 8-bit number at offset 0
alert( dataView.getUint8(0) ); // 255
// now get 16-bit number at offset 0, that's 2 bytes, both with max value
alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int)
// get 32-bit number at offset 0
alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int)
dataView.setUint32(0, 0); // set 4-byte number to zero
```
`DataView` is great when we store mixed-format data in the same buffer. E.g 8-bit integers followed by 32-bit floats, or more complex.
## Summary
`ArrayBuffer` is the core object, a raw byte sequence.
To do almost any operation on `ArrayBuffer`, we need a view.
- It can be a `TypedArray`:
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment.
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
- Or a `DataView` -- the view that uses methods to specify a format, e.g. `getUint8(offset)`.
In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed.
There are also two additional terms:
- `ArrayBufferView` is an umbrella term for all these kinds of views.
- `BufferSource` is an umbrella term for `ArrayBuffer` or `ArrayBufferView`.
These are used in descriptions of methods that operate on binary data. `BufferSource` is one of the most common words, as it means "`ArrayBuffer` or a view over it"
Here's a cheatsheet:
![](arraybuffer-view-buffersource.png)

View file

@ -1,13 +0,0 @@
function makeCounter() {
let count = 0;
function counter() {
return count++;
}
counter.set = value => count = value;
counter.decrease = () => count--;
return counter;
}

View file

@ -1,18 +0,0 @@
function makeCounter() {
let count = 0;
// ... your code ...
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
counter.set(10); // set the new count
alert( counter() ); // 10
counter.decrease(); // decrease the count by 1
alert( counter() ); // 10 (instead of 11)

View file

@ -1,41 +0,0 @@
describe("counter", function() {
it("increases from call to call", function() {
let counter = makeCounter();
assert.equal( counter(), 0 );
assert.equal( counter(), 1 );
assert.equal( counter(), 2 );
});
describe("counter.set", function() {
it("sets the count", function() {
let counter = makeCounter();
counter.set(10);
assert.equal( counter(), 10 );
assert.equal( counter(), 11 );
});
});
describe("counter.decrease", function() {
it("decreases the count", function() {
let counter = makeCounter();
counter.set(10);
assert.equal( counter(), 10 );
counter.decrease();
assert.equal( counter(), 10 );
});
});
});

View file

@ -1,13 +0,0 @@
function makeCounter() {
let count = 0;
function counter() {
return count++;
}
counter.set = value => count = value;
counter.decrease = () => count--;
return counter;
}

View file

@ -1,18 +0,0 @@
function makeCounter() {
let count = 0;
// ... your code ...
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
counter.set(10); // set the new count
alert( counter() ); // 10
counter.decrease(); // decrease the count by 1
alert( counter() ); // 10 (instead of 11)

View file

@ -1,4 +0,0 @@
# Concatenate typed arrays
Given multiple `chunks`, concatenate them into a single typed array.

View file

@ -1,41 +0,0 @@
describe("counter", function() {
it("increases from call to call", function() {
let counter = makeCounter();
assert.equal( counter(), 0 );
assert.equal( counter(), 1 );
assert.equal( counter(), 2 );
});
describe("counter.set", function() {
it("sets the count", function() {
let counter = makeCounter();
counter.set(10);
assert.equal( counter(), 10 );
assert.equal( counter(), 11 );
});
});
describe("counter.decrease", function() {
it("decreases the count", function() {
let counter = makeCounter();
counter.set(10);
assert.equal( counter(), 10 );
counter.decrease();
assert.equal( counter(), 10 );
});
});
});

View file

@ -1,129 +0,0 @@
# Binary data
There's a lot of terms in Javascript, related to binary data: files, images and so on.
To name a few:
- `ArrayBuffer`, `Uint8Array`, `BufferSource`, `DataView`, `Blob`.
There are many questions how to convert one thing into another, because people just don't spare a minute to understand.
It may seem complex, but in fact it's not. Everything's fairly simple.
Let's sort things out.
**The basic binary entity is `ArrayBuffer` -- a fixed-length raw sequence of bytes.**
We create it like this:
```js run
let buffer = new ArrayBuffer(16); // create a buffer of length 16
alert(buffer.byteLength); // 16
```
This allocates a contiguous memory area of 16 bytes and pre-fills it with zeroes.
```warn header="`ArrayBuffer` is not `Array` at all"
Please note: technically `ArrayBuffer` has nothing in common with `Array`:
- It has a fixed length, we can't increase or decrease it.
- It takes exactly that much space in the memory.
- It can only store bytes, the exact number of them.
```
**To manipulate an `ArrayBuffer`, we need to use a "view" object.**
A view object does not store anything on it's own. It's just an "eyeglasses" that give different representation of the bytes.
For instance:
- **`Uint8Array`** -- treats each byte as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so can hold only that much). That's called a "8-bit unsigned integer".
- **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer".
- **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer".
- **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from <code>5.0x10<sup>-324</sup></code> to <code>1.8x10<sup>308</sup></code>.
So, an `ArrayBuffer` of 16 bytes can be interpreted as 16 "tiny numbers", or 8 bigger numbers, or 4 even bigger, or 2 floating-point values with high precision.
![](arraybuffer-views.png)
`ArrayBuffer` is the core object, the root of everything, the raw binary data. But if we're going to write into it, or iterate over it, basically for almost any operation we must use a view, e.g:
```js run
let buffer = new ArrayBuffer(16); // create a buffer of length 16
let view = new Uint32Array(buffer); // treat every 4 bytes as an integer number
alert(view.length); // 4, it stores that many numbers
alert(view.byteLength); // 16, the length in bytes
// let's write a value
view[0] = 123456;
for(let num of view) {
alert(num); // 123456, then 0, 0, 0 (4 numbers total)
}
```
These numeric views are called "Typed Arrays".
## TypedArray
Here's a full list of typed arrays:
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment (see below).
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
`Uint8ClampedArray` has a special feature. Usually, when we write a number that doesn't fit, into an integer typed array, then its most significant bytes are cut-off.
For instance, let's try to write 256 into `Uint8Array`. In binary form, 256 is `100000000` (9 bits), but `Uint8Array` only provides 8 per value.
So only the rightmost 8 bits are kept, and the rest is cut off:
![](8bit-integer-256.png)
So we get zero. Here's the demo:
```js run
let buffer = new ArrayBuffer(16);
let uint8array = new Uint8Array(buffer);
let num = 256;
alert(num.toString(2)); // 100000000 (binary representation)
uint8array[0] = num;
alert(uint8array[0]); // 0
```
For 257, the binary form is `100000001` (9 bits), so we'll have `1` in the array:
![](8bit-integer-257.png)
In other words, the number modulo 2<sup>8</sup> is saved.
For `Uint8ClampedArray` the behavior is different. It saves 255 for any number that is greater than 255, and 0 for any negative number. That behavior is useful in image processing.
**`TypedArray` methods are similar to regular Javascript arrays, with notable exceptions.**
We can itereate, `map`, `slice`, `find`, etc.
There are few things we can't do though:
- No `splice` -- we can't "delete" a value, because typed arrays are views on a buffer, and these are contiguous areas of memory. All we can do is to assign a zero.
- No `concat` method, we can use `set` instead.
- The `set(fromArr, offset)` method copies all elements from `fromArr` to the current array, starting at position `offset`.
To concatenate several typed arrays, create a new one with the combined length and `set` onto it:
// Step 4: join chunks into result
let chunksAll = new Uint8Array(receivedLength); // (4.1)
let position = 0;
for(let chunk of chunks) {
chunksAll.set(chunk, position); // (4.2)
position += chunk.length;
}
The full list of `ArrayBufferViews`:
- `Int8Array`

View file

@ -0,0 +1,76 @@
# TextDecoder, TextEncoder
What if the binary data is actually a string?
The build-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows to read the value into an an actual Javascript string, given the buffer and the encoding.
We first need to create it:
```js
let decoder = new TextDecoder([label], [options]);
```
- **`label`** -- the encoding, `utf-8` by default.
- **`options`** -- optional object:
- **`fatal`** -- boolean, if `true` then throw an exception for invalid string, otherwise (default) replace invalid byte sequences with character `\uFFFD`.
- **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order unicode mark), we usually don't set it.
...And then decode:
```js
let str = decoder.decode([input], [options]);
```
- **`input`** -- `BufferSource` to decode.
- **`options`** -- optional object:
- **`stream`** -- true for decoding streams, when `decoder` is called repeatedly with incoming chunks of data. In that case a multi-byte character may occasionally split between chunks. This options tells `TextDecoder` to memorize "unfinished" characters and decode them when the next chunk comes.
For instance:
```js run
let uint8Array = new Uint8Array([72, 101, 108, 108, 111]);
alert( new TextDecoder().decode(uint8Array) ); // Hello
```
```js run
let uint8Array = new Uint8Array([228, 189, 160, 229, 165, 189]);
alert( new TextDecoder().decode(uint8Array) ); // 你好
```
We can decode a part of the buffer by passing it a subarray:
```js run
let uint8Array = new Uint8Array([0, 72, 101, 108, 108, 111, 0]);
// the string is in the middle
// create a new view over it, without copying anything
let binaryString = uint8Array.subarray(1, -1);
alert( new TextDecoder().decode(binaryString) ); // Hello
```
## TextEncoder
[TextEncoder](https://encoding.spec.whatwg.org/#interface-textencoder) does the reverse thing -- writes a string into a buffer.
The syntax is:
```js run
let encoder = new TextEncoder();
```
The only encoding it supports is "utf-8".
It has two methods:
- **`encode(str)`** -- returns `Uint8Array` from a string.
- **`encodeInto(str, destination)`** -- encodes `str` into `destination` that must be `Uint8Array`.
```js run
let encoder = new TextEncoder();
let uint8Array = encoder.encode("Hello");
alert(uint8Array); // 72,101,108,108,111
```

212
6-binary/03-blob/article.md Normal file
View file

@ -0,0 +1,212 @@
# Blob
`ArrayBuffer` and views are a part of ECMA standard, a part of Javascript.
In the browser, there are additional higher-level objects, described in [File API](https://www.w3.org/TR/FileAPI/).
`Blob` consists of an optional string `type` (a MIME-type usually), plus `blobParts` this a sequence of other `Blob` objects, strings and `BufferSources`.
![](blob.png)
Thanks to `type`, we can download/upload blobs, and it naturally becomes `Content-Type` in network requests.
The constructor syntax is:
```js
new Blob(blobParts, options);
```
- **`blobParts`** is an array of Blob/BufferSource/String values.
- **`options`** optional object:
- **`type`** -- blob type, usually MIME-type, e.g. `image/png`,
- **`endings`** -- whether to transform end-of-line `\r\n` to `\n` end vise versa, to make the blob correspond to current OS newlines. By default `"transparent"` (do nothing), but also can be `"native"` (transform).
Then we can access blob as a whole, or extract slices with:
```js
blob.slice([byteStart], [byteEnd], [contentType]);
```
- **`byteStart`** -- the starting byte, by default 0.
- **`byteEnd`** -- the last byte (exclusive, by default till the end).
- **`contentType`** -- the `type` of the new blob, by default the same as the source.
The arguments are similar to `array.slice`, negative numbers are allowed too.
We can't change data directly in a blob, but we can copy parts of blobs, create new blobs from them, mix them and so on. This behavior is similar to Javascript strings: we can't change a character in a string, but we can make a new corrected string.
## Blob as URL
A Blob can be easily used in an URL for `<a>`, `<img>` or other tags.
Let's start with a simple example. By clicking on a link you download a dynamically-generated blob with `hello world` contents as a file:
```html run
<!-- download attribute forces the browser to download instead of navigating -->
<a download="hello.txt" href='#' id="link">Download</a>
<script>
let binary = new Uint8Array([72, 101, 108, 108, 111]); // "hello" in binary form
// create blob of multiple parts
let blob = new Blob([binary, ' ', 'world'], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
</script>
```
We can also create a link in Javascript and simulate a click by `link.click()`, then download starts authomatically.
Here's the similar "on the fly" blob creation and download code, now without HTML:
```js run
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
```
**`URL.createObjectURL` takes a blob and creates an unique URL for it, in the form `blob:<origin>/<uuid>`.**
That's what the generated url looks like:
```
blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273
```
The browser keeps an internal url -> blob mapping. For each url generated by `URL.createObjectURL`, it stores a reference to the corresponding blob.
A generated url is only valid while the current document is open. And it allows to reference the blob in `<img>`, `<a>`, any other object that expects an URL.
There's a side-effect though. While there's an mapping for a blob, the blob itself resides in the memory. The browser can't free it.
The mapping is automatically cleared on document unload, so blobs are freed then. But if an app is long-living, then that doesn't happen. So if we create an URL, that blob will hang in memory, even if not needed any more.
**`URL.revokeObjectURL(url)` removes the reference from the internal mapping, thus allowing the blob to be deleted (if there are no other references), and the memory to be freed.**
In the last example, we intend the blob to be used only once, for instant downloading, so we call `URL.revokeObjectURL(link.href)` immediately.
In the previous example though, with the clickable HTML-link, we dont't call `URL.revokeObjectURL(link.href)`, because that would make the blob url invalid. After the revocation, as the mapping is removed, the url doesn't work any more.
## Blob to base64
An alternative to `URL.createObjectURL` is to convert a blob into a base64-encoded string.
That encoding represents binary data as a string of ultra-safe "readable" characters with ASCII-codes from 0 to 64. And what's more important -- we can use this encoding in "data-urls".
A [data url](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) has the form `data:[<mediatype>][;base64],<data>`. We can use such urls everywhere, on par with "regular" urls.
For instance, here's a smiley:
```html
<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
```
The browser will decode the string and show the image: <img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
To transform a blob into base64 and vise-versa, we'll use built-in `FileReader/FileWriter` objects. In the [next chapter](info:file) we'll cover those more in-detail. For now, it's important only that they can read a blob as data url, that we can assign it to `link.href`.
Here's the demo of downloading a blob, now via base-64:
```js run
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
*!*
let reader = new FileReader();
*/!*
reader.onload = function() {
link.href = reader.result; // data url
link.click();
};
*!*
reader.readAsDataURL(blob); // converts the blob to base64 and calls onload
*/!*
```
Both ways of making an URL of a blob are usable. But usually `URL.createObjectURL(blob)` is simpler and faster.
```compare title-plus="URL.createObjectURL(blob)" title-minus="Blob to data url"
+ We need to revoke them if care about memory.
+ Direct access to blob, no "encoding/decoding"
- No need to revoke anything.
- Performance and memory losses on big blobs for encoding.
```
## Image or a page to blob
We can create a blob of an image, or even a page screenshot.
That's usually done via `<canvas>` element:
1. We draw an existing image on canvas using [canvas.drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage).
2. Canvas [.toBlob(callback, format, quality)](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) creates a blob and calls `callback` with it.
In the example below, an image is just copied, but we could cut from it, or transform it on canvas prior to making a blob:
```js run
let img = document.querySelector('img'); // just any image
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;
let context = canvas.getContext('2d');
context.drawImage(img, 0, 0); // allows to cut image
// we can context.rotate() as well, and do many other things
canvas.toBlob(function(blob) {
// download the blob
let link = document.createElement('a');
link.download = 'example.png';
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
}, 'image/png');
```
For screenshotting a page, we can use a library such as <https://github.com/niklasvh/html2canvas> to draw the screenshot on `<canvas>`, and then download it the same way.
## From Blob to ArrayBuffer
The `Blob` constructor allows to create a blob from almost anything.
But we can also revert back to lowest-level `ArrayBuffer` using `FileReader`:
```js
// get arrayBuffer from blob
let fileReader = new FileReader();
fileReader.onload = function(event) {
let arrayBuffer = fileReader.result;
};
*!*
fileReader.readAsArrayBuffer(blob);
*/!*
```
## Summary
While `ArrayBuffer`, `Uint8Array` and other `BufferSource` are "binary data", a [Blob](https://www.w3.org/TR/FileAPI/#dfn-Blob) represents "binary data with type".
That makes Blobs convenient for upload/download operations, that are so common in the browser.
Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) and so on, can work with `Blob` natively, as well as with other binary types.
That said, it's still possible to convert betweeen `Blob` and low-level binary data types:
- We can make a Blob from a typed array using `new Blob(...)` constructor.
- We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing.

BIN
6-binary/03-blob/blob.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

106
6-binary/04-file/article.md Normal file
View file

@ -0,0 +1,106 @@
# File and FileReader
A [File](https://www.w3.org/TR/FileAPI/#dfn-file) object inhereits from `Blob`, but is extended with filesystem-related capabilities.
There are two ways to obtain it.
First, there's a constructor, similar to `Blob`:
```js
new File(fileParts, fileName, [options])
```
- **`fileParts`** -- is an array of Blob/BufferSource/String value, same as `Blob`.
- **`fileName`** -- file name string.
- **`options`** -- optional object:
- **`lastModified`** -- a timestamp (integer date) of last modification.
Second, more often we get a file from `<input type="file">` or drag'n'drop or other browser interfaces. Then the file gets these from OS.
For instance:
```html run
<input type="file" onchange="showFile(this)">
<script>
function showFile(input) {
let file = input.files[0];
alert(`File name: ${file.name}`); // e.g my.png
alert(`Last modified: ${file.lastModified}`); // e.g 1552830408824
}
</script>
```
```smart
Please note: the input may select multiple files, so `input.files` is an array-like object with them. Here we have only one file, but it's still the same, so we just take `input.files[0]`.
```
## FileReader
[FileReader](https://www.w3.org/TR/FileAPI/#dfn-filereader) is an object with the sole purpose of reading from `Blob` (and hence `File` too) objects.
It's event based, as reading from disk may take time.
The constructor:
```js
let reader = new FileReader(); // no arguments
```
The main methods:
- **`readAsArrayBuffer(blob)`** -- read the data as `ArrayBuffer`
- **`readAsText(blob, [encoding])`** -- read the data as a string (encoding is `utf-8` by default)
- **`readAsDataURL(blob)`** -- encode the data as base64 data url.
- **`abort()`** -- cancel the operation.
As the reading proceeds, there are events:
- `loadstart` -- loading started.
- `progress` -- occurs during reading.
- `load` -- no errors, reading complete.
- `abort` -- `abort()` called.
- `error` -- error has occured.
- `loadend` -- reading finished with either success or failure.
At the end:
- `reader.result` is the result (if successful)
- `reader.error` is the error (if failed).
The most widely used events are for sure `load` and `error`.
Here's an example:
```html run
<input type="file" onchange="readFile(this)">
<script>
function readFile(input) {
let file = input.files[0];
let reader = new FileReader();
reader.onload = function() {
console.log(reader.result);
};
reader.onerror = function() {
console.log(reader.error);
};
reader.readAsText(file);
}
</script>
```
```smart header="`FileReaderSync` is available for workers only"
For Web Workers, there also exists a synchronous variant of `FileReader`, called [FileReaderSync](https://www.w3.org/TR/FileAPI/#FileReaderSync).
Reading methods `read*` do not generate events, but rather return the result, as regular functions do.
That's only inside a Web Worker though, because delays and hang-ups in Web Workers are less important, they do not affect the page.
```
It most often used to read from files, and

BIN
6-binary/04-file/blob.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -60,7 +60,7 @@ To do the request, we need 3 steps:
```js ```js
xhr.onload = function() { xhr.onload = function() {
alert(`Loaded: ${xhr.status} ${xhr.responseText}`); alert(`Loaded: ${xhr.status} ${xhr.response}`);
}; };
xhr.onerror = function() { // only triggers if the request couldn't be made at all xhr.onerror = function() { // only triggers if the request couldn't be made at all
@ -69,7 +69,8 @@ To do the request, we need 3 steps:
xhr.onprogress = function(event) { // triggers periodically xhr.onprogress = function(event) { // triggers periodically
// event.loaded - how many bytes downloaded // event.loaded - how many bytes downloaded
// event.total - total number of bytes (if server set Content-Length header) // event.lengthComputable = true if the server sent Content-Length header
// event.total - total number of bytes (if lengthComputable)
alert(`Received ${event.loaded} of ${event.total}`); alert(`Received ${event.loaded} of ${event.total}`);
}; };
``` ```
@ -91,13 +92,17 @@ xhr.onload = function() {
if (xhr.status != 200) { // analyze HTTP status of the response if (xhr.status != 200) { // analyze HTTP status of the response
alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found
} else { // show the result } else { // show the result
alert(`Done, got ${xhr.responseText.length} bytes`); // responseText is the server alert(`Done, got ${xhr.response.length} bytes`); // responseText is the server
} }
}; };
xhr.onprogress = function(event) { xhr.onprogress = function(event) {
// event.total=0 if the server did not send Content-Length header if (event.lengthComputable) {
alert(`Received ${event.loaded} of ${event.total}`); alert(`Received ${event.loaded} of ${event.total} bytes`);
} else {
alert(`Received ${event.loaded} bytes`); // no Content-Length
}
}; };
xhr.onerror = function() { xhr.onerror = function() {
@ -113,10 +118,8 @@ Once the server has responded, we can receive the result in the following proper
`statusText` `statusText`
: HTTP status message (a string): usually `OK` for `200`, `Not Found` for `404`, `Forbidden` for `403` and so on. : HTTP status message (a string): usually `OK` for `200`, `Not Found` for `404`, `Forbidden` for `403` and so on.
`responseText` `response` (old scripts may use `responseText`)
: The text of the server response, if JSON, then we can `JSON.parse` it.s : The server response.
If the server returns XML with the correct header `Content-type: text/xml`, then there's also `responseXML` property with the parsed XML document. We can query it with `xhr.responseXml.querySelector("...")` and perform other XML-specific operations.
If we changed our mind, we can terminate the request at any time. The call to `xhr.abort()` does that: If we changed our mind, we can terminate the request at any time. The call to `xhr.abort()` does that:
@ -134,8 +137,46 @@ xhr.timeout = 10000; // timeout in ms, 10 seconds
If the request does not succeed within the given time, it gets canceled and `timeout` event triggers. If the request does not succeed within the given time, it gets canceled and `timeout` event triggers.
## Response Type
### Ready states We can use `xhr.responseType` property to set the response format:
- `""` (default) -- get as string,
- `"text"` -- get as string,
- `"arraybuffer"` -- get as `ArrayBuffer` (for binary data, see chapter <info:arraybuffer-and-views>),
- `"blob"` -- get as `Blob` (for binary data, see chapter <info:blob>),
- `"document"` -- get as XML document (can use XPath and other XML methods),
- `"json"` -- get as JSON (parsed automatically).
For example, let's get the response as JSON:
```js run
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/example/json');
*!*
xhr.responseType = 'json';
*/!*
xhr.send();
// the response is {"message": "Hello, world!"}
xhr.onload = function() {
let responseObj = xhr.response;
alert(responseObj.message); // Hello, world!
};
```
```smart
In the old scripts you may also find `xhr.responseText` and even `xhr.responseXML` properties.
They exist for historical reasons, to get either a string or XML document. Nowadays, we should set the format in `xhr.responseType` and get `xhr.response` as demonstrated above.
```
## Ready states
`XMLHttpRequest` changes between states as it progresses. The current state is accessible as `xhr.readyState`. `XMLHttpRequest` changes between states as it progresses. The current state is accessible as `xhr.readyState`.
@ -186,7 +227,7 @@ try {
if (xhr.status != 200) { if (xhr.status != 200) {
alert(`Error ${xhr.status}: ${xhr.statusText}`); alert(`Error ${xhr.status}: ${xhr.statusText}`);
} else { } else {
alert(xhr.responseText); alert(xhr.response);
} }
} catch(err) { // instead of onerror } catch(err) { // instead of onerror
alert("Request failed"); alert("Request failed");
@ -275,7 +316,7 @@ There are 3 methods for HTTP-headers:
}, {}); }, {});
``` ```
## POST requests ## POST, FormData
To make a POST request, we can use the built-in [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object. To make a POST request, we can use the built-in [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object.
@ -293,7 +334,7 @@ Create it, optionally from a form, `append` more fields if needed, and then:
For instance: For instance:
```html ```html run
<form name="person"> <form name="person">
<input name="name" value="John"> <input name="name" value="John">
<input name="surname" value="Smith"> <input name="surname" value="Smith">
@ -308,8 +349,10 @@ For instance:
// send it out // send it out
let xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.open("POST", "/url"); xhr.open("POST", "/article/xmlhttprequest/post/user");
xhr.send(formData); xhr.send(formData);
xhr.
</script> </script>
``` ```
@ -333,7 +376,7 @@ xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
xhr.send(json); xhr.send(json);
``` ```
`XMLHttpRequest` can also send binary data using Blob and similar objects. We'll cover binary data later, in the chapter about `fetch`. The `.send(body)` method is pretty omnivore. It can send almost everything, including Blob and BufferSource objects.
## Tracking upload progress ## Tracking upload progress
@ -371,6 +414,7 @@ xhr.upload.onerror = function() {
}; };
``` ```
## Summary ## Summary
Typical code of the GET-request with `XMLHttpRequest`: Typical code of the GET-request with `XMLHttpRequest`:
@ -389,7 +433,7 @@ xhr.onload = function() {
return; return;
} }
// get the response from xhr.responseText // get the response from xhr.response
}; };
xhr.onprogress = function(event) { xhr.onprogress = function(event) {

View file

@ -28,6 +28,14 @@ function accept(req, res) {
} }
} }
} else if (req.url == '/json') {
res.writeHead(200, {
// 'Content-Type': 'application/json;charset=utf-8',
'Cache-Control': 'no-cache'
});
res.write(JSON.stringify({message: "Hello, world!"}));
res.end();
} else { } else {
file.serve(req, res); file.serve(req, res);
} }

View file

@ -0,0 +1,36 @@
<!doctype html>
<script>
(async () {
const response = await fetch('long.txt');
const reader = response.body.getReader();
const contentLength = +response.headers.get('Content-Length');
let receivedLength = 0;
let chunks = [];
while(true) {
const chunk = await reader.read();
if (chunk.done) {
console.log("done!");
break;
}
chunks.push(chunk.value);
receivedLength += chunk.value.length;
console.log(`${receivedLength}/${contentLength} received`)
}
let chunksMerged = new Uint8Array(receivedLength);
let length = 0;
for(let chunk of chunks) {
chunksMerged.set(chunk, length);
length += chunk.length;
}
let result = new TextDecoder("utf-8").decode(chunksMerged);
console.log(result);
})();
</script>

View file

@ -0,0 +1,55 @@
let http = require('http');
let url = require('url');
let querystring = require('querystring');
let static = require('node-static');
let file = new static.Server('.', {
cache: 0
});
function accept(req, res) {
if (req.method == 'POST') {
let chunks = [];
let length = 0;
req.on('data', function (data) {
chunks.push(data);
length += data.length;
// Too much POST data, kill the connection!
if (length > 1e6) {
request.connection.destroy();
}
});
req.on('end', function() {
// let post = JSON.parse(chunks.join(''));
if (req.url == '/user') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'User saved' }));
} else if (req.url == '/image') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: "Image saved", imageSize: length }));
} else {
res.writeHead(404);
res.end("Not found");
}
});
} else {
file.serve(req, res);
}
}
// ------ запустить сервер -------
if (!module.parent) {
http.createServer(accept).listen(8080);
} else {
exports.accept = accept;
}

Binary file not shown.