Simplify with yield
Targeting modern browsers only? (Building Chrome apps, Firefox apps or Electron Desktop apps or just target modern browsers like Chrome, Opera, Firefox or Edge, Android WebView or Chrome for Mobile, or if you transpile your code from ES6 to ES5)?.
No reason to not start consuming promises using the yield keyword.
The principle is simple:
- Use the latest version of Dexie (1.3 or later)
- Use Dexie.spawn() or Dexie.async() to enable a synchronous-like programming style.
- Each method that returns a Promise can be awaited using yield.
Exactly the same way as ES7’s async/await works (but unlike async/await this works in current modern browsers without transpilation).
Sample use
var Dexie = require('dexie');
var async = Dexie.async,
spawn = Dexie.spawn;
var db = new Dexie('testdb');
db.version(1).stores({
people: '++id,name,age',
assets: '++id,ownerId,assetName'
});
spawn(function*() {
//
// Write your synchronous-like code within a spawn() block
//
// Add a person:
var id = yield db.people.add({name: 'Foo', age: 34});
// Add two assets that links to the newly added person:
yield db.assets.bulkAdd([
{assetName: "car", ownerId: id},
{assetName: "house", ownerId: id}
]);
// Now, list all people and their assets:
var people = yield db.people.toArray();
for (var i=0; i<people.length; ++i) {
var assets = yield db.assets
.where('ownerId').equals(people[i].id)
.toArray();
console.log(`${people[i].name}'s assets: ${assets.map(a => a.assetName).join(',')}`);
}
}).then(function() {
//
// spawn() returns a promise that completes when all is done.
//
console.log("Complete");
}).catch(function(e) {
//
// If any error occur in DB or plain exceptions, you
// may catch them here.
//
console.error("Failed: " + e);
});
Use in db.transaction()
Dexie.transaction() will treat generator functions (function) so that it is possible to use yield for consuming promises.
var Dexie = require('dexie');
var async = Dexie.async;
var spawn = Dexie.spawn;
var db = new Dexie("myDB");
db.version(1).stores({
friends: '++id, name, age'
});
db.open();
db.transaction('rw', db.friends, function*(){
var friendId = yield db.friends.add({name: "Foo", age: 42});
console.log("Got id: " + friendId);
console.log("These are my friends: " + JSON.stringify(yield db.friends.toArray()));
yield db.friends.delete(friendId);
console.log ("Friend successfully deleted.");
}).catch(e => alert ("Oops: " + e));
async
Marking a generator function as async() will make yield statements behave like ES7 await. This is not needed in transaction callbacks (as above sample shows) but can be used whenever you need to do several transactions, or not use transactions at all.
var Dexie = require('dexie');
var async = Dexie.async;
var spawn = Dexie.spawn;
...
var listFriends = async(function*() {
var friends = yield db.friends.toArray();
return friends;
});
listFriends()
.then(friends => console.log(JSON.stringify(friends)))
.catch(e => console.error (e.stack));
spawn
Another style is using spawn() instead of async(). Then you don’t need to store your async functions in vars.
var Dexie = require ('dexie');
var async = Dexie.async;
var spawn = Dexie.spawn;
...
function* listFriends () {
var friends = yield db.friends.toArray();
return friends;
};
spawn(listFriends)
.then(friends => console.log(JSON.stringify(friends)))
.catch(e => console.error (e.stack));
Calling Sub Functions
There are two possible of structuring your code with sub functions.
- Method 1: Declare each function with Dexie.async(). Declaring each function with async is most declarative but requires var declaration with function* expressions instead of function* statements.
- Method 2: Just declare as
function* myFunc(){ ... }
. This method gives cleaner code, but it requires the jsdocs to clarify how they are supposed to be consumed. Generator functions are not always used for emulating async/await, so it cannot be assumed that they should be called via spawn() or yield*.
Method 1
NOTE: Using ES5 style vars to make the samples work in today’s browsers (March 2016).
var async = Dexie.async;
var incrementAge = async(function* (friendId) {
yield db.friends
.where('id').equals(friendId)
.modify(friend => ++friend.age);
});
var listFriends = async(function* () {
var friends = yield db.friends.toArray();
return friends;
});
var birthdays = async(function* () {
var friends = yield listFriends();
for (var i=0; i<friends.length; ++) {
yield incrementAge(friends[i].id);
}
});
Method 2
Rule of thumb is:
- Calling a generator function will give you an Iterable, not a Promise.
- When awaiting a Promise (for example returned from Dexie API), use
yield
. - When awaiting an Iterable (the result from calling a
function*
), useyield*
function* incrementAge(friendId) {
yield db.friends
.where('id').equals(friendId)
.modify(friend => ++friend.age);
}
function* listFriends () {
var friends = yield db.friends.toArray();
return friends;
}
function* birthdays() {
var friends = yield* listFriends();
for (var i=0; i<friends.length; ++) {
yield* incrementAge(friend[i].id);
}
}
Dexie.spawn(birthdays).catch(e => console.error (e));
How this maps to ES7 async / await
Table below shows how this maps to ES7 async / await.
Using function*() and yield | Using async / await | |
Declare async function | Dexie.async(function* () {}); | async function() {} |
Declare+execute function | Dexie.spawn(function* () {}); | (async function() {})() |
Await a Promise | yield p; | await p; |
Declare Promise Generator | function* f (){} | N/A |
Await Promise Generator | yield* fn(); | N/A |
Motivation
You can also find the spawn() and async() helpers other libs like Q, Task.js etc. The reason why we need ‘yet another’ one, is because those will all return their specific types of Promises, which in some browsers are incompatible with indexedDB transactions. That’s also the main reason Dexie needs its own Promise implementation. Furthermore, Dexie Promises are capable of maintaining Promise-Specific Data (analogous to Thread-specific data) and utilize that for maintaining transaction scopes and reentrant transaction locks.
However, the Dexie versions of async() and spawn() will adapt to any promise implementation so you can use it to consume other promises as well if you like.
Table of Contents
- API Reference
- Access Control in Dexie Cloud
- Add demo users
- Add public data
- Authentication in Dexie Cloud
- Best Practices
- Building Addons
- Collection
- Collection.and()
- Collection.clone()
- Collection.count()
- Collection.delete()
- Collection.desc()
- Collection.distinct()
- Collection.each()
- Collection.eachKey()
- Collection.eachPrimaryKey()
- Collection.eachUniqueKey()
- Collection.filter()
- Collection.first()
- Collection.keys()
- Collection.last()
- Collection.limit()
- Collection.modify()
- Collection.offset()
- Collection.or()
- Collection.primaryKeys()
- Collection.raw()
- Collection.reverse()
- Collection.sortBy()
- Collection.toArray()
- Collection.uniqueKeys()
- Collection.until()
- Compound Index
- Consistency in Dexie Cloud
- Consistent add() operator
- Consistent remove() operator
- Consistent replacePrefix() operator
- Consuming Dexie as a module
- Custom Emails in Dexie Cloud
- DBCore
- DBCoreAddRequest
- DBCoreCountRequest
- DBCoreCursor
- DBCoreDeleteRangeRequest
- DBCoreDeleteRequest
- DBCoreGetManyRequest
- DBCoreGetRequest
- DBCoreIndex
- DBCoreKeyRange
- DBCoreMutateRequest
- DBCoreMutateResponse
- DBCoreOpenCursorRequest
- DBCorePutRequest
- DBCoreQuery
- DBCoreQueryRequest
- DBCoreQueryResponse
- DBCoreRangeType
- DBCoreSchema
- DBCoreTable
- DBCoreTableSchema
- DBCoreTransaction
- DBCoreTransactionMode
- DBPermissionSet
- Deprecations
- Derived Work
- Design
- Dexie Cloud API
- Dexie Cloud API Limits
- Dexie Cloud Best Practices
- Dexie Cloud CLI
- Dexie Cloud Docs
- Dexie Cloud REST API
- Dexie Cloud Web Hooks
- Dexie Constructor
- Dexie.AbortError
- Dexie.BulkError
- Dexie.ConstraintError
- Dexie.DataCloneError
- Dexie.DataError
- Dexie.DatabaseClosedError
- Dexie.IncompatiblePromiseError
- Dexie.InternalError
- Dexie.InvalidAccessError
- Dexie.InvalidArgumentError
- Dexie.InvalidStateError
- Dexie.InvalidTableError
- Dexie.MissingAPIError
- Dexie.ModifyError
- Dexie.NoSuchDatabaseErrorError
- Dexie.NotFoundError
- Dexie.Observable
- Dexie.Observable.DatabaseChange
- Dexie.OpenFailedError
- Dexie.PrematureCommitError
- Dexie.QuotaExceededError
- Dexie.ReadOnlyError
- Dexie.SchemaError
- Dexie.SubTransactionError
- Dexie.Syncable
- Dexie.Syncable.IDatabaseChange
- Dexie.Syncable.IPersistentContext
- Dexie.Syncable.ISyncProtocol
- Dexie.Syncable.StatusTexts
- Dexie.Syncable.Statuses
- Dexie.Syncable.registerSyncProtocol()
- Dexie.TimeoutError
- Dexie.TransactionInactiveError
- Dexie.UnknownError
- Dexie.UnsupportedError
- Dexie.UpgradeError()
- Dexie.VersionChangeError
- Dexie.VersionError
- Dexie.[table]
- Dexie.addons
- Dexie.async()
- Dexie.backendDB()
- Dexie.close()
- Dexie.currentTransaction
- Dexie.debug
- Dexie.deepClone()
- Dexie.defineClass()
- Dexie.delByKeyPath()
- Dexie.delete()
- Dexie.derive()
- Dexie.events()
- Dexie.exists()
- Dexie.extend()
- Dexie.fakeAutoComplete()
- Dexie.getByKeyPath()
- Dexie.getDatabaseNames()
- Dexie.hasFailed()
- Dexie.ignoreTransaction()
- Dexie.isOpen()
- Dexie.js
- Dexie.name
- Dexie.on()
- Dexie.on.blocked
- Dexie.on.close
- Dexie.on.error
- Dexie.on.populate
- Dexie.on.populate-(old-version)
- Dexie.on.ready
- Dexie.on.storagemutated
- Dexie.on.versionchange
- Dexie.open()
- Dexie.override()
- Dexie.semVer
- Dexie.setByKeyPath()
- Dexie.shallowClone()
- Dexie.spawn()
- Dexie.table()
- Dexie.tables
- Dexie.transaction()
- Dexie.transaction()-(old-version)
- Dexie.use()
- Dexie.verno
- Dexie.version
- Dexie.version()
- Dexie.vip()
- Dexie.waitFor()
- DexieCloudOptions
- DexieError
- Docs Home
- Download
- EntityTable
- Export and Import Database
- Get started with Dexie in Angular
- Get started with Dexie in React
- Get started with Dexie in Svelte
- Get started with Dexie in Vue
- Hello World
- How To Use the StorageManager API
- Inbound
- IndexSpec
- Indexable Type
- IndexedDB on Safari
- Invite
- Member
- Migrating existing DB to Dexie
- MultiEntry Index
- PersistedSyncState
- Privacy Policy
- Promise
- Promise.PSD
- Promise.catch()
- Promise.finally()
- Promise.on.error
- Promise.onuncatched
- Questions and Answers
- Realm
- Releasing Dexie
- Road Map
- Road Map: Dexie 5.0
- Road Map: Dexie Cloud
- Role
- Run Dexie Cloud on Own Servers
- Sharding and Scalability
- Simplify with yield
- Support Ukraine
- SyncState
- Table
- Table Schema
- Table.add()
- Table.bulkAdd()
- Table.bulkDelete()
- Table.bulkGet()
- Table.bulkPut()
- Table.bulkUpdate()
- Table.clear()
- Table.count()
- Table.defineClass()
- Table.delete()
- Table.each()
- Table.filter()
- Table.get()
- Table.hook('creating')
- Table.hook('deleting')
- Table.hook('reading')
- Table.hook('updating')
- Table.limit()
- Table.mapToClass()
- Table.name
- Table.offset()
- Table.orderBy()
- Table.put()
- Table.reverse()
- Table.schema
- Table.toArray()
- Table.toCollection()
- Table.update()
- Table.where()
- The main limitations of IndexedDB
- Transaction
- Transaction.abort()
- Transaction.on.abort
- Transaction.on.complete
- Transaction.on.error
- Transaction.table()
- Tutorial
- Typescript
- Typescript (old)
- Understanding the basics
- UserLogin
- Version
- Version.stores()
- Version.upgrade()
- WhereClause
- WhereClause.above()
- WhereClause.aboveOrEqual()
- WhereClause.anyOf()
- WhereClause.anyOfIgnoreCase()
- WhereClause.below()
- WhereClause.belowOrEqual()
- WhereClause.between()
- WhereClause.equals()
- WhereClause.equalsIgnoreCase()
- WhereClause.inAnyRange()
- WhereClause.noneOf()
- WhereClause.notEqual()
- WhereClause.startsWith()
- WhereClause.startsWithAnyOf()
- WhereClause.startsWithAnyOfIgnoreCase()
- WhereClause.startsWithIgnoreCase()
- db.cloud.configure()
- db.cloud.currentUser
- db.cloud.currentUserId
- db.cloud.events.syncComplete
- db.cloud.invites
- db.cloud.login()
- db.cloud.logout()
- db.cloud.options
- db.cloud.permissions()
- db.cloud.persistedSyncState
- db.cloud.roles
- db.cloud.schema
- db.cloud.sync()
- db.cloud.syncState
- db.cloud.userInteraction
- db.cloud.usingServiceWorker
- db.cloud.version
- db.cloud.webSocketStatus
- db.members
- db.realms
- db.roles
- db.syncable.connect()
- db.syncable.delete()
- db.syncable.disconnect()
- db.syncable.getOptions()
- db.syncable.getStatus()
- db.syncable.list()
- db.syncable.on('statusChanged')
- db.syncable.setFilter()
- dexie-cloud-addon
- dexie-react-hooks
- liveQuery()
- unhandledrejection-event
- useLiveQuery()
- useObservable()
- usePermissions()