Promises

Упрощаем использование параллельных потоков в Javascript

| Категории: Javascript
Анна Аминева

Иллюстрация блокнота

Промисы - представление некоего значения в будущем. Они отличаются от типичного для Node.js стиля с огромным количеством колбэков (Callback hell), потому что они дают вам реальные значения, которые вы можете продолжать использовать.
Несколько примеров:

Промисы представляют «обещание» будущего значения

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var pinky = require('pinky')
var fs = require('fs')
// You just return a placeholder for your value,
// then fulfill or reject your placeholder
// depending on the asynchronous operation later on
function read(filename) {
var promise = pinky()
fs.readFile(filename, function(error, buffer) {
if (error) promise.reject(error)
else promise.fulfill(buffer)
})
return promise
}

Промисы можно комбинировать

Потому что они являются реальными значениями, точно так же как String или Array:

1
2
3
4
5
6
7
8
function decode(encoding, buffer) {
// We put things into a Promise, so we can
// accept both real buffers *and* eventual ones :D
return pinky(buffer).then(function(buffer){
return buffer.toString(encoding)
})
}
var data = decode('utf-8', read('foo.txt'))

Промисы можно использовать где угодно, ведь они - это значения

1
2
3
4
5
6
7
8
9
10
11
12
13
// This means we can make any function
// accept a promise without changing any
// of its code :
D
function lift2(a, b, f) {
return pinky(a).then(function(valueA) {
return pinky(b).then(function(valueB) {
return pinky(f(valueA, valueB))
})
})
})
function concat(a, b) { return a + b }
var fooBar = lift2(data, fs.readFileSync('bar.txt', 'utf-8'), concat)

Просто создать новые комбинаторы

Все вышеперечисленные свойства облегчают эту задачу. Бонус: структурирует ваш запутанный код.

Все вышеперечисленные свойства облегчают задачу. Бонус: весь ваш запутанный код разделяется!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function pipeline(fns) {
return fns.reduce(function(promise, f){
return promise.then(f)
}, promise(undefined) }
}
// This looks better with currying, but you
// can use `.bind(null, x, y)`
pipeline( read('foo.txt')
, decode('utf-8')
, splitLines
, map(toUpperCase)
, joinLines
, write('screaming.txt'))
// Or in parallel
parallel( read('foo.txt')
, read('bar.txt')
, read('baz.txt'))
.then(function(foo, bar, baz) {
return foo + ';' + bar + ';' + baz
})
// Or in any other order you want, just
// build relationships between
// the values using `.then()` and the
// promise library will figure it out :D

Промисы стандартизированы

Выберите любую библиотеку реализующую промисы и вы сможете работать с асинхронным кодом. В добавок, если вы пишите комбинатор для promises, он будет работать везде, а не только в вашей библиотеке: https://github.com/killdream/pinky-combinators

Работа без колбэков в Nodejs без проблем

Если вы используете Node.js, то вы можете создать комбинатор, который позволит отказаться от колбэков всего с помощью 5 строчек кода, но мы уже сделали это за вас:

1
2
3
4
5
6
7
8
9
10
#### λ lift-node
# Lifts a Node-style function into a function yielding a Promise.
#
# :: (a..., ((Error c, b) -> ())) -> Promise a c... -> Promise b c
lift-node = (f) -> (...args) ->
promise = pinky!
(all args) .then (as) -> f ...as, (err, data) ->
| err => promise.reject err
| _ => promise.fulfill data
return promise

#####Ссылки и дополнительный материал

Несомненно обратите внимание на спецификацию и на библиотеки, которые ее реализуют.

Отличные статьи по теме включают пост James Coglan и пост Irakli

Читайте так же статьи по теме: