mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-22 06:59:30 -06:00
chore: refactor cy funcs (#19080)
Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com> Co-authored-by: David Munechika <david@cypress.io>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash'
|
||||
import $Command from '../../../src/cypress/command'
|
||||
import $CommandQueue from '../../../src/cypress/command_queue'
|
||||
import { CommandQueue } from '../../../src/cypress/command_queue'
|
||||
|
||||
const createCommand = (props = {}) => {
|
||||
return $Command.create(_.extend({
|
||||
@@ -23,14 +23,14 @@ const log = (props = {}) => {
|
||||
describe('src/cypress/command_queue', () => {
|
||||
let queue
|
||||
const state = () => {}
|
||||
const timeouts = { timeout () {} }
|
||||
const stability = { whenStable () {} }
|
||||
const timeout = () => {}
|
||||
const whenStable = () => {}
|
||||
const cleanup = () => {}
|
||||
const fail = () => {}
|
||||
const isCy = () => {}
|
||||
|
||||
beforeEach(() => {
|
||||
queue = $CommandQueue.create(state, timeouts, stability, cleanup, fail, isCy)
|
||||
queue = new CommandQueue(state, timeout, whenStable, cleanup, fail, isCy)
|
||||
|
||||
queue.add(createCommand({
|
||||
name: 'get',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Bluebird from 'bluebird'
|
||||
|
||||
import $Queue from '../../../src/util/queue'
|
||||
import { Queue } from '../../../src/util/queue'
|
||||
|
||||
const ids = (queueables) => queueables.map((q) => q.id)
|
||||
|
||||
@@ -8,7 +8,7 @@ describe('src/util/queue', () => {
|
||||
let queue
|
||||
|
||||
beforeEach(() => {
|
||||
queue = $Queue.create([
|
||||
queue = new Queue([
|
||||
{ id: '1' },
|
||||
{ id: '2' },
|
||||
{ id: '3' },
|
||||
|
||||
@@ -14,7 +14,7 @@ import browserInfo from './cypress/browser'
|
||||
import $scriptUtils from './cypress/script_utils'
|
||||
|
||||
import $Commands from './cypress/commands'
|
||||
import $Cy from './cypress/cy'
|
||||
import { $Cy } from './cypress/cy'
|
||||
import $dom from './dom'
|
||||
import $Downloads from './cypress/downloads'
|
||||
import $errorMessages from './cypress/error_messages'
|
||||
@@ -209,12 +209,8 @@ class $Cypress {
|
||||
// or parsed. we have not received any custom commands
|
||||
// at this point
|
||||
onSpecWindow (specWindow, scripts) {
|
||||
const logFn = (...args) => {
|
||||
return this.log.apply(this, args)
|
||||
}
|
||||
|
||||
// create cy and expose globally
|
||||
this.cy = $Cy.create(specWindow, this, this.Cookies, this.state, this.config, logFn)
|
||||
this.cy = new $Cy(specWindow, this, this.Cookies, this.state, this.config)
|
||||
window.cy = this.cy
|
||||
this.isCy = this.cy.isCy
|
||||
this.log = $Log.create(this, this.cy, this.state, this.config)
|
||||
|
||||
@@ -3,7 +3,7 @@ import $ from 'jquery'
|
||||
import Bluebird from 'bluebird'
|
||||
import Debug from 'debug'
|
||||
|
||||
import $queue from '../util/queue'
|
||||
import { Queue } from '../util/queue'
|
||||
import $dom from '../dom'
|
||||
import $utils from './utils'
|
||||
import $errUtils from './error_utils'
|
||||
@@ -60,336 +60,322 @@ const commandRunningFailed = (Cypress, state, err) => {
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
create: (state, timeout, whenStable, cleanup, fail, isCy) => {
|
||||
const queue = $queue.create()
|
||||
export class CommandQueue extends Queue<Command> {
|
||||
state: any
|
||||
timeout: any
|
||||
whenStable: any
|
||||
cleanup: any
|
||||
fail: any
|
||||
isCy: any
|
||||
|
||||
const { get, slice, at, reset, clear, stop } = queue
|
||||
constructor (state, timeout, whenStable, cleanup, fail, isCy) {
|
||||
super()
|
||||
this.state = state
|
||||
this.timeout = timeout
|
||||
this.whenStable = whenStable
|
||||
this.cleanup = cleanup
|
||||
this.fail = fail
|
||||
this.isCy = isCy
|
||||
}
|
||||
|
||||
const logs = (filter) => {
|
||||
let logs = _.flatten(_.invokeMap(queue.get(), 'get', 'logs'))
|
||||
logs (filter) {
|
||||
let logs = _.flatten(_.invokeMap(this.get(), 'get', 'logs'))
|
||||
|
||||
if (filter) {
|
||||
const matchesFilter = _.matches(filter)
|
||||
if (filter) {
|
||||
const matchesFilter = _.matches(filter)
|
||||
|
||||
logs = _.filter(logs, (log) => {
|
||||
return matchesFilter(log.get())
|
||||
})
|
||||
}
|
||||
|
||||
return logs
|
||||
}
|
||||
|
||||
const names = () => {
|
||||
return _.invokeMap(queue.get(), 'get', 'name')
|
||||
}
|
||||
|
||||
const add = (command) => {
|
||||
queue.add(command)
|
||||
}
|
||||
|
||||
const insert = (index: number, command: Command) => {
|
||||
queue.insert(index, command)
|
||||
|
||||
const prev = at(index - 1) as Command
|
||||
const next = at(index + 1) as Command
|
||||
|
||||
if (prev) {
|
||||
prev.set('next', command)
|
||||
command.set('prev', prev)
|
||||
}
|
||||
|
||||
if (next) {
|
||||
next.set('prev', command)
|
||||
command.set('next', next)
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
const find = (attrs) => {
|
||||
const matchesAttrs = _.matches(attrs)
|
||||
|
||||
return _.find(queue.get(), (command: Command) => {
|
||||
return matchesAttrs(command.attributes)
|
||||
logs = _.filter(logs, (log) => {
|
||||
return matchesFilter(log.get())
|
||||
})
|
||||
}
|
||||
|
||||
const runCommand = (command: Command) => {
|
||||
// bail here prior to creating a new promise
|
||||
// because we could have stopped / canceled
|
||||
// prior to ever making it through our first
|
||||
// command
|
||||
if (queue.stopped) {
|
||||
return logs
|
||||
}
|
||||
|
||||
names () {
|
||||
return _.invokeMap(this.get(), 'get', 'name')
|
||||
}
|
||||
|
||||
insert (index: number, command: Command) {
|
||||
super.insert(index, command)
|
||||
|
||||
const prev = this.at(index - 1)
|
||||
const next = this.at(index + 1)
|
||||
|
||||
if (prev) {
|
||||
prev.set('next', command)
|
||||
command.set('prev', prev)
|
||||
}
|
||||
|
||||
if (next) {
|
||||
next.set('prev', command)
|
||||
command.set('next', next)
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
find (attrs) {
|
||||
const matchesAttrs = _.matches(attrs)
|
||||
|
||||
return _.find(this.get(), (command: Command) => {
|
||||
return matchesAttrs(command.attributes)
|
||||
})
|
||||
}
|
||||
|
||||
private runCommand (command: Command) {
|
||||
// bail here prior to creating a new promise
|
||||
// because we could have stopped / canceled
|
||||
// prior to ever making it through our first
|
||||
// command
|
||||
if (this.stopped) {
|
||||
return
|
||||
}
|
||||
|
||||
this.state('current', command)
|
||||
this.state('chainerId', command.get('chainerId'))
|
||||
|
||||
return this.whenStable(() => {
|
||||
this.state('nestedIndex', this.state('index'))
|
||||
|
||||
return command.get('args')
|
||||
})
|
||||
.then((args) => {
|
||||
// store this if we enqueue new commands
|
||||
// to check for promise violations
|
||||
let ret
|
||||
let enqueuedCmd
|
||||
|
||||
const commandEnqueued = (obj) => {
|
||||
return enqueuedCmd = obj
|
||||
}
|
||||
|
||||
// only check for command enqueuing when none
|
||||
// of our args are functions else commands
|
||||
// like cy.then or cy.each would always fail
|
||||
// since they return promises and queue more
|
||||
// new commands
|
||||
if ($utils.noArgsAreAFunction(args)) {
|
||||
Cypress.once('command:enqueued', commandEnqueued)
|
||||
}
|
||||
|
||||
// run the command's fn with runnable's context
|
||||
try {
|
||||
ret = __stackReplacementMarker(command.get('fn'), this.state('ctx'), args)
|
||||
} catch (err) {
|
||||
throw err
|
||||
} finally {
|
||||
// always remove this listener
|
||||
Cypress.removeListener('command:enqueued', commandEnqueued)
|
||||
}
|
||||
|
||||
this.state('commandIntermediateValue', ret)
|
||||
|
||||
// we cannot pass our cypress instance or our chainer
|
||||
// back into bluebird else it will create a thenable
|
||||
// which is never resolved
|
||||
if (this.isCy(ret)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!(!enqueuedCmd || !$utils.isPromiseLike(ret))) {
|
||||
$errUtils.throwErrByPath(
|
||||
'miscellaneous.command_returned_promise_and_commands', {
|
||||
args: {
|
||||
current: command.get('name'),
|
||||
called: enqueuedCmd.name,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (!(!enqueuedCmd || !!_.isUndefined(ret))) {
|
||||
ret = _.isFunction(ret) ?
|
||||
ret.toString() :
|
||||
$utils.stringify(ret)
|
||||
|
||||
// if we got a return value and we enqueued
|
||||
// a new command and we didn't return cy
|
||||
// or an undefined value then throw
|
||||
return $errUtils.throwErrByPath(
|
||||
'miscellaneous.returned_value_and_commands_from_custom_command', {
|
||||
args: {
|
||||
current: command.get('name'),
|
||||
returned: ret,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return ret
|
||||
}).then((subject) => {
|
||||
this.state('commandIntermediateValue', undefined)
|
||||
|
||||
// we may be given a regular array here so
|
||||
// we need to re-wrap the array in jquery
|
||||
// if that's the case if the first item
|
||||
// in this subject is a jquery element.
|
||||
// we want to do this because in 3.1.2 there
|
||||
// was a regression when wrapping an array of elements
|
||||
const firstSubject = $utils.unwrapFirst(subject)
|
||||
|
||||
// if ret is a DOM element and its not an instance of our own jQuery
|
||||
if (subject && $dom.isElement(firstSubject) && !$utils.isInstanceOf(subject, $)) {
|
||||
// set it back to our own jquery object
|
||||
// to prevent it from being passed downstream
|
||||
// TODO: enable turning this off
|
||||
// wrapSubjectsInJquery: false
|
||||
// which will just pass subjects downstream
|
||||
// without modifying them
|
||||
subject = $dom.wrap(subject)
|
||||
}
|
||||
|
||||
command.set({ subject })
|
||||
|
||||
// end / snapshot our logs if they need it
|
||||
command.finishLogs()
|
||||
|
||||
// reset the nestedIndex back to null
|
||||
this.state('nestedIndex', null)
|
||||
|
||||
// also reset recentlyReady back to null
|
||||
this.state('recentlyReady', null)
|
||||
|
||||
// we're finished with the current command so set it back to null
|
||||
this.state('current', null)
|
||||
|
||||
this.state('subject', subject)
|
||||
|
||||
return subject
|
||||
})
|
||||
}
|
||||
|
||||
// TypeScript doesn't allow overriding functions with different type signatures
|
||||
// @ts-ignore
|
||||
run () {
|
||||
const next = () => {
|
||||
// bail if we've been told to abort in case
|
||||
// an old command continues to run after
|
||||
if (this.stopped) {
|
||||
return
|
||||
}
|
||||
|
||||
state('current', command)
|
||||
state('chainerId', command.get('chainerId'))
|
||||
// start at 0 index if we dont have one
|
||||
let index = this.state('index') || this.state('index', 0)
|
||||
|
||||
return whenStable(() => {
|
||||
state('nestedIndex', state('index'))
|
||||
const command = this.at(index)
|
||||
|
||||
return command.get('args')
|
||||
})
|
||||
.then((args) => {
|
||||
// store this if we enqueue new commands
|
||||
// to check for promise violations
|
||||
let ret
|
||||
let enqueuedCmd
|
||||
// if the command should be skipped
|
||||
// just bail and increment index
|
||||
// and set the subject
|
||||
if (command && command.get('skip')) {
|
||||
// must set prev + next since other
|
||||
// operations depend on this state being correct
|
||||
command.set({
|
||||
prev: this.at(index - 1),
|
||||
next: this.at(index + 1),
|
||||
})
|
||||
|
||||
const commandEnqueued = (obj) => {
|
||||
return enqueuedCmd = obj
|
||||
}
|
||||
this.state('index', index + 1)
|
||||
this.state('subject', command.get('subject'))
|
||||
|
||||
// only check for command enqueuing when none
|
||||
// of our args are functions else commands
|
||||
// like cy.then or cy.each would always fail
|
||||
// since they return promises and queue more
|
||||
// new commands
|
||||
if ($utils.noArgsAreAFunction(args)) {
|
||||
Cypress.once('command:enqueued', commandEnqueued)
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
||||
// run the command's fn with runnable's context
|
||||
try {
|
||||
ret = __stackReplacementMarker(command.get('fn'), state('ctx'), args)
|
||||
} catch (err) {
|
||||
throw err
|
||||
} finally {
|
||||
// always remove this listener
|
||||
Cypress.removeListener('command:enqueued', commandEnqueued)
|
||||
}
|
||||
// if we're at the very end
|
||||
if (!command) {
|
||||
// trigger queue is almost finished
|
||||
Cypress.action('cy:command:queue:before:end')
|
||||
|
||||
state('commandIntermediateValue', ret)
|
||||
// we need to wait after all commands have
|
||||
// finished running if the application under
|
||||
// test is no longer stable because we cannot
|
||||
// move onto the next test until its finished
|
||||
return this.whenStable(() => {
|
||||
Cypress.action('cy:command:queue:end')
|
||||
|
||||
// we cannot pass our cypress instance or our chainer
|
||||
// back into bluebird else it will create a thenable
|
||||
// which is never resolved
|
||||
if (isCy(ret)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!(!enqueuedCmd || !$utils.isPromiseLike(ret))) {
|
||||
return $errUtils.throwErrByPath(
|
||||
'miscellaneous.command_returned_promise_and_commands', {
|
||||
args: {
|
||||
current: command.get('name'),
|
||||
called: enqueuedCmd.name,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (!(!enqueuedCmd || !!_.isUndefined(ret))) {
|
||||
ret = _.isFunction(ret) ?
|
||||
ret.toString() :
|
||||
$utils.stringify(ret)
|
||||
|
||||
// if we got a return value and we enqueued
|
||||
// a new command and we didn't return cy
|
||||
// or an undefined value then throw
|
||||
return $errUtils.throwErrByPath(
|
||||
'miscellaneous.returned_value_and_commands_from_custom_command', {
|
||||
args: {
|
||||
current: command.get('name'),
|
||||
returned: ret,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return ret
|
||||
}).then((subject) => {
|
||||
state('commandIntermediateValue', undefined)
|
||||
|
||||
// we may be given a regular array here so
|
||||
// we need to re-wrap the array in jquery
|
||||
// if that's the case if the first item
|
||||
// in this subject is a jquery element.
|
||||
// we want to do this because in 3.1.2 there
|
||||
// was a regression when wrapping an array of elements
|
||||
const firstSubject = $utils.unwrapFirst(subject)
|
||||
|
||||
// if ret is a DOM element and its not an instance of our own jQuery
|
||||
if (subject && $dom.isElement(firstSubject) && !$utils.isInstanceOf(subject, $)) {
|
||||
// set it back to our own jquery object
|
||||
// to prevent it from being passed downstream
|
||||
// TODO: enable turning this off
|
||||
// wrapSubjectsInJquery: false
|
||||
// which will just pass subjects downstream
|
||||
// without modifying them
|
||||
subject = $dom.wrap(subject)
|
||||
}
|
||||
|
||||
command.set({ subject })
|
||||
|
||||
// end / snapshot our logs if they need it
|
||||
command.finishLogs()
|
||||
|
||||
// reset the nestedIndex back to null
|
||||
state('nestedIndex', null)
|
||||
|
||||
// also reset recentlyReady back to null
|
||||
state('recentlyReady', null)
|
||||
|
||||
// we're finished with the current command so set it back to null
|
||||
state('current', null)
|
||||
|
||||
state('subject', subject)
|
||||
|
||||
return subject
|
||||
})
|
||||
}
|
||||
|
||||
const run = () => {
|
||||
const next = () => {
|
||||
// bail if we've been told to abort in case
|
||||
// an old command continues to run after
|
||||
if (queue.stopped) {
|
||||
return
|
||||
}
|
||||
|
||||
// start at 0 index if we dont have one
|
||||
let index = state('index') || state('index', 0)
|
||||
|
||||
const command = at(index) as Command
|
||||
|
||||
// if the command should be skipped
|
||||
// just bail and increment index
|
||||
// and set the subject
|
||||
if (command && command.get('skip')) {
|
||||
// must set prev + next since other
|
||||
// operations depend on this state being correct
|
||||
command.set({
|
||||
prev: at(index - 1) as Command,
|
||||
next: at(index + 1) as Command,
|
||||
})
|
||||
|
||||
state('index', index + 1)
|
||||
state('subject', command.get('subject'))
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
// if we're at the very end
|
||||
if (!command) {
|
||||
// trigger queue is almost finished
|
||||
Cypress.action('cy:command:queue:before:end')
|
||||
|
||||
// we need to wait after all commands have
|
||||
// finished running if the application under
|
||||
// test is no longer stable because we cannot
|
||||
// move onto the next test until its finished
|
||||
return whenStable(() => {
|
||||
Cypress.action('cy:command:queue:end')
|
||||
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
// store the previous timeout
|
||||
const prevTimeout = timeout()
|
||||
|
||||
// store the current runnable
|
||||
const runnable = state('runnable')
|
||||
|
||||
Cypress.action('cy:command:start', command)
|
||||
|
||||
return runCommand(command)
|
||||
.then(() => {
|
||||
// each successful command invocation should
|
||||
// always reset the timeout for the current runnable
|
||||
// unless it already has a state. if it has a state
|
||||
// and we reset the timeout again, it will always
|
||||
// cause a timeout later no matter what. by this time
|
||||
// mocha expects the test to be done
|
||||
let fn
|
||||
|
||||
if (!runnable.state) {
|
||||
timeout(prevTimeout)
|
||||
}
|
||||
|
||||
// mutate index by incrementing it
|
||||
// this allows us to keep the proper index
|
||||
// in between different hooks like before + beforeEach
|
||||
// else run will be called again and index would start
|
||||
// over at 0
|
||||
index += 1
|
||||
state('index', index)
|
||||
|
||||
Cypress.action('cy:command:end', command)
|
||||
|
||||
fn = state('onPaused')
|
||||
|
||||
if (fn) {
|
||||
return new Bluebird((resolve) => {
|
||||
return fn(resolve)
|
||||
}).then(next)
|
||||
}
|
||||
|
||||
return next()
|
||||
})
|
||||
}
|
||||
|
||||
const onError = (err: Error | string) => {
|
||||
if (state('onCommandFailed')) {
|
||||
return state('onCommandFailed')(err, queue, next)
|
||||
// store the previous timeout
|
||||
const prevTimeout = this.timeout()
|
||||
|
||||
// store the current runnable
|
||||
const runnable = this.state('runnable')
|
||||
|
||||
Cypress.action('cy:command:start', command)
|
||||
|
||||
return this.runCommand(command)
|
||||
.then(() => {
|
||||
// each successful command invocation should
|
||||
// always reset the timeout for the current runnable
|
||||
// unless it already has a state. if it has a state
|
||||
// and we reset the timeout again, it will always
|
||||
// cause a timeout later no matter what. by this time
|
||||
// mocha expects the test to be done
|
||||
let fn
|
||||
|
||||
if (!runnable.state) {
|
||||
this.timeout(prevTimeout)
|
||||
}
|
||||
|
||||
debugErrors('caught error in promise chain: %o', err)
|
||||
// mutate index by incrementing it
|
||||
// this allows us to keep the proper index
|
||||
// in between different hooks like before + beforeEach
|
||||
// else run will be called again and index would start
|
||||
// over at 0
|
||||
index += 1
|
||||
this.state('index', index)
|
||||
|
||||
// since this failed this means that a specific command failed
|
||||
// and we should highlight it in red or insert a new command
|
||||
// @ts-ignore
|
||||
if (_.isObject(err) && !err.name) {
|
||||
// @ts-ignore
|
||||
err.name = 'CypressError'
|
||||
Cypress.action('cy:command:end', command)
|
||||
|
||||
fn = this.state('onPaused')
|
||||
|
||||
if (fn) {
|
||||
return new Bluebird((resolve) => {
|
||||
return fn(resolve)
|
||||
}).then(next)
|
||||
}
|
||||
|
||||
commandRunningFailed(Cypress, state, err)
|
||||
return next()
|
||||
})
|
||||
}
|
||||
|
||||
return fail(err)
|
||||
const onError = (err: Error | string) => {
|
||||
if (this.state('onCommandFailed')) {
|
||||
return this.state('onCommandFailed')(err, this, next)
|
||||
}
|
||||
|
||||
const { promise, reject, cancel } = queue.run({
|
||||
onRun: next,
|
||||
onError,
|
||||
onFinish: cleanup,
|
||||
})
|
||||
debugErrors('caught error in promise chain: %o', err)
|
||||
|
||||
state('promise', promise)
|
||||
state('reject', reject)
|
||||
state('cancel', () => {
|
||||
cancel()
|
||||
// since this failed this means that a specific command failed
|
||||
// and we should highlight it in red or insert a new command
|
||||
// @ts-ignore
|
||||
if (_.isObject(err) && !err.name) {
|
||||
// @ts-ignore
|
||||
err.name = 'CypressError'
|
||||
}
|
||||
|
||||
Cypress.action('cy:canceled')
|
||||
})
|
||||
commandRunningFailed(Cypress, this.state, err)
|
||||
|
||||
return promise
|
||||
return this.fail(err)
|
||||
}
|
||||
|
||||
return {
|
||||
logs,
|
||||
names,
|
||||
add,
|
||||
insert,
|
||||
find,
|
||||
run,
|
||||
get,
|
||||
slice,
|
||||
at,
|
||||
reset,
|
||||
clear,
|
||||
stop,
|
||||
const { promise, reject, cancel } = super.run({
|
||||
onRun: next,
|
||||
onError,
|
||||
onFinish: this.cleanup,
|
||||
})
|
||||
|
||||
get length () {
|
||||
return queue.length
|
||||
},
|
||||
this.state('promise', promise)
|
||||
this.state('reject', reject)
|
||||
this.state('cancel', () => {
|
||||
cancel()
|
||||
|
||||
get stopped () {
|
||||
return queue.stopped
|
||||
},
|
||||
}
|
||||
},
|
||||
Cypress.action('cy:canceled')
|
||||
})
|
||||
|
||||
return promise
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,111 +6,102 @@ interface QueueRunProps {
|
||||
onFinish: () => void
|
||||
}
|
||||
|
||||
export default {
|
||||
create: <T>(queueables: T[] = []) => {
|
||||
let stopped = false
|
||||
export class Queue<T> {
|
||||
private queueables: T[] = []
|
||||
private _stopped = false
|
||||
|
||||
const get = (): T[] => {
|
||||
return queueables
|
||||
constructor (queueables: T[] = []) {
|
||||
this.queueables = queueables
|
||||
}
|
||||
|
||||
get (): T[] {
|
||||
return this.queueables
|
||||
}
|
||||
|
||||
add (queueable: T) {
|
||||
this.queueables.push(queueable)
|
||||
}
|
||||
|
||||
insert (index: number, queueable: T) {
|
||||
if (index < 0 || index > this.queueables.length) {
|
||||
throw new Error(`queue.insert must be called with a valid index - the index (${index}) is out of bounds`)
|
||||
}
|
||||
|
||||
const add = (queueable: T) => {
|
||||
queueables.push(queueable)
|
||||
}
|
||||
this.queueables.splice(index, 0, queueable)
|
||||
|
||||
const insert = (index: number, queueable: T) => {
|
||||
if (index < 0 || index > queueables.length) {
|
||||
throw new Error(`queue.insert must be called with a valid index - the index (${index}) is out of bounds`)
|
||||
}
|
||||
return queueable
|
||||
}
|
||||
|
||||
queueables.splice(index, 0, queueable)
|
||||
slice (index: number) {
|
||||
return this.queueables.slice(index)
|
||||
}
|
||||
|
||||
return queueable
|
||||
}
|
||||
at (index: number): T {
|
||||
return this.queueables[index]
|
||||
}
|
||||
|
||||
const slice = (index: number) => {
|
||||
return queueables.slice(index)
|
||||
}
|
||||
reset () {
|
||||
this._stopped = false
|
||||
}
|
||||
|
||||
const at = (index: number): T => {
|
||||
return get()[index]
|
||||
}
|
||||
clear () {
|
||||
this.queueables.length = 0
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
stopped = false
|
||||
}
|
||||
stop () {
|
||||
this._stopped = true
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
queueables.length = 0
|
||||
}
|
||||
run ({ onRun, onError, onFinish }: QueueRunProps) {
|
||||
let inner
|
||||
let rejectOuterAndCancelInner
|
||||
|
||||
const stop = () => {
|
||||
stopped = true
|
||||
}
|
||||
// this ends up being the parent promise wrapper
|
||||
const promise = new Bluebird((resolve, reject) => {
|
||||
// bubble out the inner promise. we must use a resolve(null) here
|
||||
// so the outer promise is first defined else this will kick off
|
||||
// the 'next' call too soon and end up running commands prior to
|
||||
// the promise being defined
|
||||
inner = Bluebird
|
||||
.resolve(null)
|
||||
.then(onRun)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
|
||||
const run = ({ onRun, onError, onFinish }: QueueRunProps) => {
|
||||
let inner
|
||||
let rejectOuterAndCancelInner
|
||||
|
||||
// this ends up being the parent promise wrapper
|
||||
const promise = new Bluebird((resolve, reject) => {
|
||||
// bubble out the inner promise. we must use a resolve(null) here
|
||||
// so the outer promise is first defined else this will kick off
|
||||
// the 'next' call too soon and end up running commands prior to
|
||||
// the promise being defined
|
||||
inner = Bluebird
|
||||
.resolve(null)
|
||||
.then(onRun)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
|
||||
// can't use onCancel argument here because it's called asynchronously.
|
||||
// when we manually reject our outer promise we have to immediately
|
||||
// cancel the inner one else it won't be notified and its callbacks
|
||||
// will continue to be invoked. normally we don't have to do this
|
||||
// because rejections come from the inner promise and bubble out to
|
||||
// our outer, but when we manually reject the outer promise, we
|
||||
// have to go in the opposite direction from outer -> inner
|
||||
rejectOuterAndCancelInner = (err) => {
|
||||
inner.cancel()
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
.catch(onError)
|
||||
.finally(onFinish)
|
||||
|
||||
const cancel = () => {
|
||||
promise.cancel()
|
||||
// can't use onCancel argument here because it's called asynchronously.
|
||||
// when we manually reject our outer promise we have to immediately
|
||||
// cancel the inner one else it won't be notified and its callbacks
|
||||
// will continue to be invoked. normally we don't have to do this
|
||||
// because rejections come from the inner promise and bubble out to
|
||||
// our outer, but when we manually reject the outer promise, we
|
||||
// have to go in the opposite direction from outer -> inner
|
||||
rejectOuterAndCancelInner = (err) => {
|
||||
inner.cancel()
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
.catch(onError)
|
||||
.finally(onFinish)
|
||||
|
||||
return {
|
||||
promise,
|
||||
cancel,
|
||||
// wrapped to ensure `rejectOuterAndCancelInner` is assigned
|
||||
// before reject is called
|
||||
reject: (err) => rejectOuterAndCancelInner(err),
|
||||
}
|
||||
const cancel = () => {
|
||||
promise.cancel()
|
||||
inner.cancel()
|
||||
}
|
||||
|
||||
return {
|
||||
get,
|
||||
add,
|
||||
insert,
|
||||
slice,
|
||||
at,
|
||||
reset,
|
||||
clear,
|
||||
stop,
|
||||
run,
|
||||
|
||||
get length () {
|
||||
return queueables.length
|
||||
},
|
||||
|
||||
get stopped () {
|
||||
return stopped
|
||||
},
|
||||
promise,
|
||||
cancel,
|
||||
// wrapped to ensure `rejectOuterAndCancelInner` is assigned
|
||||
// before reject is called
|
||||
reject: (err) => rejectOuterAndCancelInner(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
get length () {
|
||||
return this.queueables.length
|
||||
}
|
||||
|
||||
get stopped () {
|
||||
return this._stopped
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user