Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Promise polyfill (https://javascript.ru/forum/project/46096-promise-polyfill.html)

Octane 28.03.2014 05:03

Promise polyfill
 
Тут задавали вопрос, что внутри Deferred, а еще нативная поддержка Promise появилась в браузерах. Чтобы окончательно разобраться в «обещаниях», решил свой полифил написать

ES6 Promise polyfill на GitHub, для работы требуется setImmediate.
Реализовано поведение, максимально похожее на native Promise в Chrome и Aurora.
Можно использовать в Nodejs:
var Promise = require('es6-promises');

Установка:

Bower
bower install promises

npm
npm install es6-promises

danik.js 28.03.2014 09:56

По поводу полифилов. Ты зачем перезаписываешь свойства:
Object.keys = Object.keys || function()..

Это вобще дикость какая-то:
Object(object) !== object


Кстати, может кто пояснить, в каком стандарте писаны методы Array.slice и прочие?

monolithed 28.03.2014 10:35

Цитата:

Сообщение от Octane
В IE8 почему-то не работает

Может по тому что мало тестов? :)

nerv_ 28.03.2014 13:11

Добавлю ссылок:
An open standard for sound, interoperable JavaScript promises—by implementers, for implementers
MDN Promise

---

Object(this).constructor !== Promise
// ->
isPromise(object)


-

такие штуки тоже не люблю
var results = Array(promises.length);

на мой взгляд new надо писать для наглядности

-

ну и по возможности я бы запихнул функции
function nextResolve(data) {
function nextReject(error) {
function resolve(data) {
function reject(error) {
в прототип как приватные (чтобы не создавались каждый раз)

-

а в целом выглядит симпатично :)
Еще я вспомнил, что у тебя была тема про асинхронность. Так вот setImmediate там бы вписалась очень хорошо)

---

Цитата:

Сообщение от danik.js
Кстати, может кто пояснить, в каком стандарте писаны методы Array.slice и прочие?

предположу, что ни к каком. Это чтобы каждый раз из прототипа не брать, насколько я понимаю.

Цитата:

Сообщение от danik.js
Это вобще дикость какая-то:
Object(object) !== object

я за isObject(value) :)

---

GOD, пошел отсюда далеко и надолго :)

Octane 28.03.2014 16:47

Цитата:

Сообщение от danik.js
По поводу полифилов. Ты зачем перезаписываешь свойства:
Object.keys = Object.keys || function()..

Что изменится от того, что keys станет enumerable? Ну и вообще я методы побыстрому навыдергивал в пример отсюда, там есть проверка.


Цитата:

Сообщение от nerv_
Цитата:

Сообщение от danik.js
Кстати, может кто пояснить, в каком стандарте писаны методы Array.slice и прочие?

предположу, что ни к каком. Это чтобы каждый раз из прототипа не брать, насколько я понимаю.

Array generic methods

В Aurora 29
Array.slice.toString()
"function slice() {
    [native code]
}"



Цитата:

Сообщение от nerv_
Цитата:

Сообщение от danik.js
Это вобще дикость какая-то:
Object(object) !== object

я за isObject(value)

хз Object(object) === object стандартная проверка, сто лет так делаю, до этого никто не удивлялся :)
http://kangax.github.io/es5-compat-table/
http://kangax.github.io/es5-compat-table/es6/
http://kangax.github.io/es5-compat-table/non-standard/
http://people.mozilla.org/~jorendorf...ototype-object
не нахожу здесь Object.isObject или чего-то подобного, в глобале создавать такую функцию не хочу, а с неймспейсом запись не намного короче будет. Мне больше нравится запись if (Object(arg).length), чем if (isObject(arg) && arg.length).
Object.is не подходит, потому что Object.is(null, null) → true. Поэтому не вижу особой необходимости создавать isObject для полифила, в библиотеке или фреймворке — без проблем.

Цитата:

Сообщение от nerv_
Object(this).constructor !== Promise
// ->
isPromise(object)

Здесь не просто this.constructor, потому что в strict режиме this может быть undefined.
Да isPromise добавлю, думаю ни один раз понадобится, там надо разобраться, что происходит, когда в дженерики promise передают. Или на this instanceof Promise заменю.

Цитата:

Сообщение от nerv_
ну и по возможности я бы запихнул функции
function nextResolve(data) {
function nextReject(error) {
function resolve(data) {
function reject(error) {

в прототип как приватные (чтобы не создавались каждый раз)

попробую, просто сначала кроме onFulfilled, onRejected и lastData еще куча переменных замыкалась (всякие lastError, состояния rejected и fulfilled, и др.), сохранять их все куда то не хотелось

Цитата:

Сообщение от nerv_
Еще я вспомнил, что у тебя была тема про асинхронность. Так вот setImmediate там бы вписалась очень хорошо)

setImmediate есть в polyfills.js, используется в Promise и реализован почти так же, как в той теме

danik.js 28.03.2014 17:41

Цитата:

Сообщение от Octane
Array generic methods

Цитата:

These are currently not part of ECMAScript standards
Ясно. Кстати, неужели Mozilla не могут исправить это положение? Ведь и вправду удобно.
Цитата:

Сообщение от Octane
Object(object) === object

Первый раз вижу. Ну может погорячился насчет дикости. А кто-нибудь из форумчан еще использует? А в либах мож каких известных?

Octane 28.03.2014 18:06

Цитата:

Сообщение от danik.js
Ясно. Кстати, неужели Mozilla не могут исправить это положение? Ведь и вправду удобно.

Причем запись о дженериках появилась примерно год назад, всякие непонятно зачем нужные copyWithin в стандарте появляются, а действительно полезные методы нет :(

Цитата:

Сообщение от danik.js
Первый раз вижу. Ну может погорячился насчет дикости. А кто-нибудь из форумчан еще использует? А в либах мож каких известных?

Кажется я узнал о таком способе когда-то давно из здешней темы по поводу typeof null → "object". Обсуждали как этот вариант лучше пропускать. В prototype.js полно таких проверок.

monolithed 29.03.2014 00:20

Цитата:

Сообщение от Octane
всякие непонятно зачем нужные copyWithin в стандарте появляются, а действительно полезные методы нет

Лучше бы вместо него System.arraycopy из java.util.Arrays добавили :)

nerv_ 29.03.2014 01:18

Цитата:

Сообщение от Octane
Здесь не просто this.constructor, потому что в strict режиме this может быть undefined.

я про это ничего не говорил. Найди 10 отличий http://learn.javascript.ru/play/K9WRM :)

Цитата:

Сообщение от Octane
setImmediate есть в polyfills.js, используется в Promise и реализован почти так же, как в той теме

да, я обратил внимание, что хаки для его реализации можно пересчитать, использую пальцы одной руки

Octane 29.03.2014 01:53

Цитата:

Сообщение от nerv_
Найди 10 отличий http://learn.javascript.ru/play/K9WRM

isObject и isPromise нашел)

Octane 30.03.2014 05:17

Короче магическим образом заставил IE8 работать: переименовал параметры функции и параметр в try-catch, так чтобы они не совпадали. Повторить баг не получилось, но reason не понятно когда становился undefined :D
function (reason) {

	…

	try {

		…

	}
	catch (*!*reason*/!*) {

		…

	}

	…
 
}


вторая причина тут http://javascript.ru/forum/misc/4588...-funkcijj.html

nerv_ 01.04.2014 00:58

Накопилось много вопросов. Мужайся :)

1. Закончен ли полифил? (ие8 меня не интересует)
2. Покрыт ли он тестами?
3. Будет ли выложен на гитхаб?
4. Планируется ли адаптация (setImmediate -> nextTick, ...) под node.js?

Octane 01.04.2014 01:44

Цитата:

Сообщение от nerv_
1. Закончен ли полифил? (ие8 меня не интересует)

Кроме метода cast и thenable value, остальное реализовано, поведение как в Aurora 29 и Chrome 35 (resolver синхронный, resolve и reject асинхронные). Метода cast нет ни в Aurora, ни в Chrome, а последний еще и не умеет промисы делать из объектов с методом then, так что не знаю, стоит ли пытаться дополнять нативную реализацию?

Цитата:

Сообщение от nerv_
2. Покрыт ли он тестами?

я слишком ленивый :( надо будет с чужого полифила тесты забрать)

Цитата:

Сообщение от nerv_
3. Будет ли выложен на гитхаб?

https://github.com/Octane/jsCore/blo...ill/promise.js

Цитата:

Сообщение от nerv_
4. Планируется ли адаптация (setImmediate -> nextTick, ...) под node.js?

достаточно:
var setImmediate = process.nextTick;

ну и там всякие Object.assign и Array generic methods


npm установщик никогда не делал, надо будет разбираться



--------------------------------
хм
Цитата:

Promise.cast is renamed to Promise.resolve
https://mail.mozilla.org/pipermail/e...ry/036194.html

а тут http://esdiscuss.org/topic/promise-c...romise-resolve вроде спорят, что это разные методы были


------------------
чорт баг нашел: resolver только один раз может выполнится, то есть реюз обещания не возможен, но при этом then'ы все должны выполнятся, даже если promise уже settled

kobezzza 01.04.2014 09:13

Цитата:

Планируется ли адаптация (setImmediate -> nextTick, ...) под node.js?
Дык в ноде из коробки setImmediate есть же уже давно.
А ваще: https://github.com/NobleJS/setImmediate

Octane 01.04.2014 09:52

О точно есть, забыл, что в node-webkit его из global надо вытаскивать, когда проверял

Octane 02.04.2014 02:59

Короче у меня уже promise мозга :D

Чтобы сделать обещания одноразовыми, почти все заново переписал…

nerv_ 03.04.2014 14:52

Цитата:

Сообщение от Octane
Короче у меня уже promise мозга

А ты как думал, лезть в обещания :)

nerv_ 08.05.2014 14:51

Octane, сейчас пытаюсь установить твой promise-polyfill и понимаю, что это не так просто сделать :)

Ситуация такая: я использую es5-shim, т.е. es5 у меня можно сказать есть. Для того, чтобы твой полифил запустился, мне необходимо еще установить:
Object.assign
Array.slice
Array.forEach
Array.every
window.setImmediate

Можно это дело отразить хотя бы в комментариях к полифилу? А то получается, каждый разработчик, кот. захочет воспользоваться твоим promise-полифилом, обязан прочитать код и проверить его на наличие es6 фич в том случае, если он не использует все твои полифилы.
И идеале, было бы хорошо ссылок набросать на required для es3 и es5 для данного полифила.

Лично я бы выкинул из реализации Object.assign, т.к. без него можно обойтись.
Иными словами, ты заставляешь меня тянуть Object.assign :)
А также
Array.slice
Array.forEach
Array.every

несмотря на то, что es5 у меня есть.

Octane 08.05.2014 15:14

Как?! как можно жить без Object.assign?! :D Да, вечером оформлю.
Пока что вот ссылки на гитхаб:

Object.assign
//using Object.keys: goo.gl/0QNMDz
//several sources: twitter.com/rwaldron/status/454114058640183296
//people.mozilla.org/~jorendorff/es6-draft.html#sec-object.assign
if (!Object.assign) {
    Object.assign = function (target) {
        Array.prototype.slice.call(arguments, 1).forEach(function (source) {
            Object.keys(source).forEach(function (key) {
                target[key] = source[key];
            });
        });
        return target;
    };
}

Array generics
//Array generic methods
new function () {

    function fastApply(method, args) {
        var target = args[0];
        switch (args.length) {
            case 1:
                return method.call(target);
            case 2:
                return method.call(target, args[1]);
            case 3:
                return method.call(target, args[1], args[2]);
        }
        return method.apply(target, Array.prototype.slice.call(args, 1));
    }

    function createGeneric(method) {
        return function () {
            return fastApply(method, arguments);
        };
    }

    function createGenerics(source, names) {
        return names.reduce(function (methods, name) {
            methods[name] = createGeneric(source[name]);
            return methods;
        }, {});
    }

    function implement(object, methods) {
        Object.keys(methods).forEach(function (name) {
            if (!(name in object)) {
                object[name] = methods[name];
            }
        });
    }

    implement(Array, createGenerics(Array.prototype, [
        'concat', 'every', 'fill', 'filter', 'find',
        'findIndex', 'forEach', 'indexOf', 'join',
        'lastIndexOf', 'map', 'pop', 'push', 'reduce',
        'reduceRight', 'reverse', 'shift', 'slice',
        'some', 'sort', 'splice', 'unshift'
    ]));

};

setImmediate IE9+
window.setImmediate || Object.assign(window, window.msSetImmediate ? {

    //IE10
    setImmediate: window.msSetImmediate,
    clearImmediate: window.msClearImmediate

} : new function () {

    var id = 0,
        storage = {},
        firstCall = true,
        message = 'setImmediatePolyfillMessage';

    function fastApply(args) {
        var func = args[0];
        switch (args.length) {
            case 1:
                return func();
            case 2:
                return func(args[1]);
            case 3:
                return func(args[1], args[2]);
        }
        return func.apply(window, Array.prototype.slice.call(args, 1));
    }

    function callback(event) {
        var data,
            key = event.data;
        if ('string' == typeof key && key.startsWith(message)) {
            data = storage[key];
            if (data) {
                fastApply(data);
                delete storage[key];
            }
        }
    }

    return {

        setImmediate: function () {
            var key = message + ++id;
            storage[key] = arguments;
            if (firstCall) {
                firstCall = false;
                window.addEventListener('message', callback);
            }
            window.postMessage(key, '*');
            return id;
        },

        clearImmediate: function (id) {
            delete storage[message + id];
        }

    };

});


сам Promise polyfill
правда я там в последней версии дописал window. перед setImmediate, потому что 'setImmediate' in Window.prototype → true, не знаю, может зря так сделал, если кто в Nodejs захочет подключить, работать не будет, хотя для ноды и так миллион полифилов есть… сделать через global?

----------
Наверное отдельный самодостаточный репозиторий на гитхабе сделаю

nerv_ 08.05.2014 15:37

Я еще немного покритиХую :)

---

На мой взгляд, ты заставляешь тянуть es6 код в проект. Не каждому проекту он нужен. Поэтому, как мне кажется, логичным было бы сделать возможность его не тянуть. Например, в виде версий для es5 и es6.

По поводу ноды - я за универсальность (по возможности). Допустим, есть миллион полифилов, но я работаю с твоим на клиенте. Я уже знаю, что он фуричит, проверен. Почему бы не использовать его и на сервере?

Octane 08.05.2014 16:14

Promise уже не ES5, логично предполагать наличие es6-shim, но не составит труда убрать ES6-Object.assign и мозиловские Array generics. Ок, сделаю отдельно, будет только @requires setImmediate.

У меня опыт Nodejs только в составе node-webkit, поэтому особо не суюсь в серверные скрипты.

Octane 08.05.2014 20:45

Хы пока выносил в отдельный репозиторий, нашел различие в поведении нативной реализации в Chrome и Firefox:

var p1 = Promise.resolve(),
    p2 = Promise.resolve();

p1.then(p2).then(function () {
    console.log('Firefox!'); //сработает только в Firefox
});


и что-то не найду, как должно быть, везде описан случай:
var p1 = Promise.resolve(),
    p2 = Promise.resolve();

p1.then(function () {
    return p2;
}).then(function () {
    console.log('Works well!'); //тут оба браузера сработают, но не полифил
});
щас буду исправлять :(

---------------

Отдельный репозиторий Promise не требующий Object.assign и Array generics.
В ноде подключается так:
var Promise = require('es6-promises').Promise;

monolithed 08.05.2014 21:51

Octane,
Крутяк :victory:

nerv_ 08.05.2014 22:14

Цитата:

Сообщение от Octane
Отдельный репозиторий Promise не требующий Object.assign и Array generics.
В ноде подключается так:
var Promise = require('promise').Promise;

спасибо, добрый человек :thanks:

Octane 08.05.2014 22:15

Спасибо)

--------------------------------------
var p1 = new Promise(function (resolve) {
        resolve();
    }),
    p2 = new Promise(function (resolve) {
        resolve();
    });

p1.then(p2).then(function () {
    console.log('Works'); //Aurora 31
}, function () {
    console.log('Error'); //Chrome 36
});
и какому варианту следовать? :D

nerv_ 08.05.2014 22:18

Цитата:

Сообщение от Octane
и какому варианту следовать?

любит, не любит :)

monolithed 09.05.2014 16:01

Octane,
а ты этот проект видел https://github.com/slightlyoff/Promises ?

Octane 09.05.2014 17:38

Я много пересмотрел, в этом какие-то нестандартные Promise.any, Promise.every, Promise.some, Promise.fulfill. Наверное еще по ранним черновикам делал, или сами названия выдумывал. Странно конечно, это же участник ТС39.

monolithed 09.05.2014 17:49

Цитата:

Сообщение от Octane
Я много пересмотрел, в этом какие-то нестандартные Promise.any, Promise.every, Promise.some, Promise.fulfill. Наверное еще по ранним черновикам делали, или сами названия выдумывали.

Ты обратил внимание на то, что они завязываются на process.nextTick || MutationObserver?


Может для this._* сделать неперечисляемыми?
package.json сделаешь? :)

Octane 09.05.2014 18:10

process.nextTick это для старых версий Nodejs, сейчас там встроен global.setImmediate. MutationObserver я хз для чего там, поиск не нашел postMessage, наверное события обсервера используют чтобы асинхронно вызывать функции, ну или хз надо будет внимательнее посмотреть.

установку через npm и bower сделаю, как баг исправлю.

Цитата:

Сообщение от monolithed
Может для this._* сделать неперечисляемыми?

это же придется подменять defineProperty для IE8…

monolithed 09.05.2014 18:31

Цитата:

Сообщение от Octane
это же придется подменять defineProperty для IE8…

Кому нужно подключают полифил для ES5 :)

Цитата:

Сообщение от Octane
как баг исправлю.

Сколько бы я не видел реализаций промисов, только в $.Deferred нет этого бага.

Octane 09.05.2014 19:15

у меня тоже не было этого бага, пока не добавил параметр _defer, чтобы resolver при создании promise сразу запускался, а не при первом вызове then:

вся логика в then, только он запускает resolver, чтобы обойтись малой кровью, добавил параметр _defer
function Promise(resolver, _defer) {
    ...
    return _defer ? this ? this.then();
}
Promise.prototype.then = function () {
    ...
    resolver(...)
    ...
    return new Promise(..., _defer = true);
};
но теперь всплыл этот баг.

Maxmaxmaximus100 09.05.2014 20:37

неужели промисы так сложно реализуются О_О
у меня промисы получались в 50 строк кода со всеми плюшками.

сделать?

Octane 09.05.2014 21:00

Конечно сделай :)


Что-то у меня голова сегодня не варит ничего исправлять, запилил пока что установку:

Bower
bower install promises

npm
npm install es6-promises

используем модуль
var Promise = require('es6-promises').Promise;



------------
а readme тут https://www.npmjs.org/package/es6-promises само обновится со временем или надо пепубликовать?

monolithed 09.05.2014 22:31

Цитата:

Сообщение от Octane
var Promise = require('es6-promises').Promise;

А почему так? ;)

Цитата:

Сообщение от Octane
а readme тут https://www.npmjs.org/package/es6-promises само обновится со временем или надо пепубликовать?

Само, но лучше добавь таск в Grunt чтобы публикация в npm происходила автоматически серез Travis CI (если все тесты Ок).

Octane 09.05.2014 22:34

а как должно быть?)

npm же всегда папку создает, просто promise уже занято конечно, пришлось так назвать

monolithed 09.05.2014 23:58

Цитата:

Сообщение от Octane
а как должно быть?)

var Promise = require('es6-promises');

PS: глянь как у меня релиз публикуется :)

Octane 10.05.2014 07:02

ага, спасибо, разобрался, теперь работает так.
ошибку с settled promise тоже исправил

monolithed 10.05.2014 09:58

setImmediate = (global.window || global).setImmediate,

Почему не написать посто global.setImmediate?

monolithed 10.05.2014 14:15

А что у тебя с версионностью?

Добавь release таск, он автоматически будет менять циферки и публиковать в npm.

Сейчас получается, что ты изменения внес, а npm их нет! ;)


Часовой пояс GMT +3, время: 01:03.