2015-02-07 2 views
9

Я видел sync-promise, размещенную на Reddit и got into a discussion with the author. Мы заметили некоторые странные несоответствия в отношениях между транзакциями IndexedDB и обещаниями.Несогласованное взаимодействие между транзакциями IndexedDB и Promises

Индексированные транзакции DB автоматически фиксируются при завершении всех событий onsuccess. Одно из осложнений состоит в том, что вы не можете делать асинхронные операции в обратном вызове onsuccess, за исключением выполнения другой операции в той же транзакции. Например, вы не можете запустить запрос AJAX в onsuccess, а затем повторно использовать ту же транзакцию после запроса AJAX, который возвращает некоторые данные.

Что с этим связано? Насколько я понимаю, обещание разрешения всегда должно быть асинхронным. Это означает, что вы не можете использовать обещания без автоматической фиксации транзакции IndexedDB.

Here is an example of what I'm talking about:

var openRequest = indexedDB.open("library"); 

openRequest.onupgradeneeded = function() { 
    // The database did not previously exist, so create object stores and indexes. 
    var db = openRequest.result; 
    var store = db.createObjectStore("books", {keyPath: "isbn"}); 
    var titleIndex = store.createIndex("by_title", "title", {unique: true}); 
    var authorIndex = store.createIndex("by_author", "author"); 

    // Populate with initial data. 
    store.put({title: "Quarry Memories", author: "Fred", isbn: 123456}); 
    store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567}); 
    store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678}); 
}; 

function getByTitle(tx, title) { 
    return new Promise(function(resolve, reject) { 
    var store = tx.objectStore("books"); 
    var index = store.index("by_title"); 
    var request = index.get("Bedrock Nights"); 
    request.onsuccess = function() { 
     var matching = request.result; 
     if (matching !== undefined) { 
     // A match was found. 
     resolve(matching); 
     } else { 
     // No match was found. 
     console.log('no match found'); 
     } 
    }; 
    }); 
} 

openRequest.onsuccess = function() { 
    var db = openRequest.result; 
    var tx = db.transaction("books", "readonly"); 
    getByTitle(tx, "Bedrock Nights").then(function(book) { 
    console.log('First book', book.isbn, book.title, book.author); 
    return getByTitle(tx, "Quarry Memories"); 
    }).then(function(book) { 
    console.log('Second book', book.isbn, book.title, book.author); 
    // With native promises this gives the error: 
    // InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable 
    // With bluebird everything is fine 
    }); 
}; 

(Полное раскрытие: демонстрация была создана paldepind, а не я)

Я пробовал его в Chrome и Firefox. В Firefox он не работает из-за транзакции, но он действительно работает в Chrome! Какое поведение верно? И если поведение Firefox правильное, разве буквально невозможно использовать «правильные» реализации обещаний с транзакциями IndexedDB?

Другое осложнение: если я загружаю bluebird перед запуском вышеуказанной демонстрации, он работает как в Chrome, так и в Firefox. Означает ли это, что синяя птица решает обещать синхронно? Я думал, что это не должно было это делать!

JSFiddle

+0

Конструктор обещаний всегда называется синхронно (в bluebird и в нативных обещаниях). Bluebird - это просто синяя птица - она ​​не разрешается синхронно (тогда все еще выполняются на следующей итерации цепочки. Это говорит о том, что я не понимаю, что проблема с IndexedDB и A + обещает очень хорошо. Я посмотрю, смогу ли я отследить некоторых умных людей. –

ответ

7

Это, вероятно, из-за the difference between microtasks and tasks ("macrotasks"). В Firefox никогда не было реализации обещаний по стандартным жалобам, в которых используются микротовары, тогда как Chrome, Bluebird и другие правильно используют микротовары. Вы можете видеть это в том, как микрозадача (которая выполняется «раньше», чем макрозадача, но все еще асинхронная) попадает внутрь границы транзакции, тогда как макрозадача (например, из обещаний Firefox) этого не делает.

Итак, это ошибка Firefox.

+0

Спасибо за информацию! У вас есть ссылка на эту ошибку в Firefox? Я нашел [эту связанную дискуссию с прошлого года] (http://lists.w3.org/Archives/Public/public-webapps/2014AprJun/0811 .html), и, похоже, он не достигает разрешения. В любом случае кажется, что большинство людей говорят, что текущее поведение Chrome неверно. Возможно, я неправильно понимаю. – dumbmatter

5

Итак, я снова глубоко погрузился в спецификации IndexedDB, DOM и HTML. Мне действительно нужно получить это право для SyncedDB, поскольку он в значительной степени полагается на обещания внутри транзакций.

Суть проблемы заключается в том, является ли задержка выполнения onFulfilled и onRejected обратных вызовов к then, обещающим/А + совместимому должен обладать вызовет транзакцию IndexedDB фиксацию.

Правилами IndexedDB на всю жизнь сделок на самом деле довольно прямо вперед, когда вы извлечь их из спецификации и выровнять их:

  • Запрос может осуществляться только в отношении сделки, когда его активный флаг установлен в true (as specified here).
  • При создании транзакции изначально активен, пока элемент управления не возвращается в цикл событий браузеров (this is specified in the transaction creation steps).
  • При каждом запуске события успешной или ошибки транзакции активным флагом присваивается значение true перед тем, как отправляется последний шаг перед отправкой события. После отправки события транзакция помечается как неактивной снова (this is specified in the steps for firing a success/error event
  • Когда транзакция больше не может активизироваться автоматически не будут фиксироваться (as specified here)

Это примерно переводится:..

  • При создании транзакции вы можете разместить столько запросов, сколько пожелаете.
  • С этого момента новый запрос может быть сделан только внутри обработчиков событий для других запросов success или error прослушиватель событий.
  • Когда все запросы выполнены и новые запросы не будут отправлены, транзакция будет выполнена.

Тогда возникает вопрос: если обещание выполнено в request «s слушателя success или error событие будет его onFulfilled обратные вызовы будут вызываться перед IndexedDB снова устанавливает транзакцию как неактивные? То есть будет onFullfilled обратные вызовы будут называться как часть шага 3 в firing a success event?

Шаг отправляет событие, а IndexedDB использует события DOM, поэтому фактическая выполняемая операция выходит за пределы спецификации IndexedDB. Шаги для отправки события, а не specified here in the DOM specification. Перейдя по шагам, становится ясно, что ни в коем случае не выполняется микрозадача (которая вызовет ответные обратные вызовы). Поэтому первоначальный вывод заключается в том, что транзакция будет закрыта до того, как будут вызваны вызовы onFulfilled.

Однако если мы прикрепляем обработчик событий, указав атрибут onsuccess на request объекте вещей становится все более волосатым. В этом случае мы не просто adding an event listener согласно спецификации DOM. Вместо этого мы устанавливаем event handler IDL attribute, как определено в спецификации HTML.

Когда мы это делаем, обратный вызов не добавляется непосредственно в список прослушивателей событий. Вместо этого он «обернут» внутри the event handlers processing algorithm. Этот алгоритм выполняет следующие важные операции:

  1. На шаге 3 он запускает jump to code entry-point algorithm.
  2. Затем выполняется этапы clean up after running a callback, которые
  3. И наконец, это выполняет microtask checkpoint. Это означает, что ваши ответные вызовы будут вызваны до того, как транзакция будет отмечена как неактивная! Ура!

Это хорошая новость! Но странно, как ответ зависит от того, слушаете ли вы событие success с помощью addEventListener или установите обработчик события onsuccess. Если вы выполняете первую транзакцию, транзакция должна быть неактивной, когда вызывается ответный вызов onFulfilled вашего обещания, и если вы делаете это позже, он все равно должен быть активным.

Я был, однако не смог воспроизвести разницу в существующих браузерах.С собственными обещаниями Firefox не работает в коде кода, несмотря ни на что, и Chrome преуспевает даже при использовании addEventListener. Возможно, что я пропустил или неправильно понял что-то в спецификациях.

В качестве окончательного примечания Bluebird обещает закрыть транзакции в Internet Explorer 11. Это связано с планированием, которое Bluebird использует в IE. Мой synchronized promise implementation работает внутри транзакций в IE.

+0

Вам не нужна отдельная реализация синхронных обещаний, вы можете сделать копию сибирской синхронной, вызывая 'Promise.setScheduler (function (fn) {fn();})' – Esailija

+0

Вы даже не должны этого делать. Bluebird имеет синхронизированную сборку, но SyncPromise предназначен для библиотеки и в зависимости от ap В таком прецеденте нежелательна огромная полнофункциональная библиотека обещаний. – paldepind

4

Вы правы: обещания разрешаются асинхронно, а IndexedDB имеет некоторые синхронные требования. В то время как другие ответы указывают на то, что собственные обещания могут корректно работать с IndexedDB в некоторых версиях определенных браузеров, вполне вероятно, что вам придется иметь дело с проблемой, что она не работает в некоторых браузерах, на которые вы нацеливаете.

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

Существует довольно простой обходной путь, однако: использовать библиотеку Promise, которая обеспечивает способ явно смывать его обратный вызов очереди и обертку IndexedDB, который очищает очередь обратного вызова обещания после вызова функции обратного вызова события.

С точки зрения Promises/A + нет никакой разницы между обработчиками, вызываемыми в конце события, или в начале следующего цикла цикла - они все еще вызываются после того, как все код, который настроил обратные вызовы, завершился, что является важной частью асинхронности Promise.

Это позволяет использовать обещания, которые являются асинхронными, в смысле удовлетворения всех гарантий Promises/A +, но которые все еще гарантируют, что транзакция IndexedDB не будет закрыта. Таким образом, вы все равно получаете все преимущества обратных вызовов, которые не происходят «сразу».

Уловка состоит в том, что вам нужны библиотеки, которые поддерживают это, и не каждая реализация Promise предоставляет способ указания планировщика или сброса его очереди обратного вызова. Аналогичным образом, я не знаю об открытых оболочках с индексом IndexedDB с открытым исходным кодом, которые поддерживают это.

Если вы пишете собственную обертку IndexedDB с помощью Promsies, было бы полезно использовать соответствующую реализацию Promise и соответственно потопить свою очередь обратного вызова. Одним из простых вариантов было бы внедрение одной из многих «микропроцессорных» реализаций, которые составляют всего лишь 100 строк Javascript, и модифицировать их по мере необходимости. В качестве альтернативы, использование одной из более крупных основных библиотек Promise с поддержкой пользовательских расписаний было бы выполнимо.

У нет использовать библиотеку синхронных обещаний, синхронную сборку Bluebird или синхронный планировщик. Если вы это сделаете, вы можете вообще отказаться от обещаний и использовать прямые обратные вызовы.

Последующее примечание: один комментатор предполагает, что синхронное обещание столь же безопасно, как и сброс очереди обратного вызова. Но они ошибаются. Ужасно, ужасно неправильно. Вы можете рассуждать о одиночном обработчике достаточно хорошо, чтобы сказать: «здесь нет никакого другого кода, теперь можно вызвать обратные вызовы». Для проведения аналогичного анализа с синхронными обещаниями требуется полное понимание того, как все называет все остальное ... что прямо противоположно тому, что вам нужно в первую очередь.

В конкретной реализации синхронного обещания автор sync-обещаний утверждает, что их библиотека обещаний теперь «безопасна» и не «освобождает Zalgo».Они снова ошибаются: это не безопасно, и делает релиз Zalgo. Автор, по-видимому, на самом деле не понял статьи о «выпуске Zalgo» и успешно переопределил jQuery обещания, которые широко считаются ужасно разбитыми по ряду причин, в том числе их Zalgo-ness.

Синхронные обещания просто небезопасны, независимо от вашей реализации.

+0

Спасибо! Можете ли вы быть более явными для newb, как я? Взятие Bluebird в качестве примера ... [это позволяет вам указать планировщик] (https://github.com/petkaantonov/bluebird/blob/master/API.md#promisesetschedulerfunction-scheduler---void). Достаточно ли этого, чтобы транзакция IDB не была закрыта? Если да, то какова реализация (paldepind ниже говорит [по умолчанию] (https://github.com/petkaantonov/bluebird/blob/master/src/schedule.js) не работает для IE11)? Если нет, то какой дополнительный контроль потребуется, и есть ли пример библиотеки, которая обеспечивает эту функциональность? – dumbmatter

+0

Если вы используете синхронную реализацию обещаний в контролируемой ограниченной форме, я бы сказал, что это далеко не ужасная идея. Вы вводите _exactly_ те же опасности в свой код, если вы начинаете ручную очистку очереди обратного вызова. Не являются также оптимальными решениями. Но в случае, когда вы пишете обертку IndexedDB, вам придется сбрасывать почти каждый раз, когда вы разрешаете обещание. Пропущенный? Вы только что внесете ошибку! Таким образом, ваше решение фактически увеличивает как количество кода, так и вероятность ошибок. – paldepind

+0

В настоящее время библиотека [htps://github.com/paldepind/sync-promise] теперь содержит механизм безопасности, который делает его _safer_, чем ручная очистка очереди обратного вызова. – paldepind

Смежные вопросы