From e7e88126adb157ded7936bf385272675484571c2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 13 Apr 2018 10:17:20 +0200 Subject: [PATCH] add FileOpenFlags, FileErrors, and a bit of polish, #47475 --- src/vs/base/node/encoding.ts | 6 +- src/vs/platform/files/common/files.ts | 25 ++++++++- src/vs/vscode.proposed.d.ts | 24 +++++++- .../electron-browser/mainThreadFileSystem.ts | 10 ++-- src/vs/workbench/api/node/extHost.api.impl.ts | 3 + src/vs/workbench/api/node/extHost.protocol.ts | 6 +- .../workbench/api/node/extHostFileSystem.ts | 16 +++--- .../electron-browser/remoteFileService.ts | 55 ++++++++++--------- .../files/electron-browser/streams.ts | 30 +++++----- 9 files changed, 108 insertions(+), 67 deletions(-) diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 80da2a7c5c1d1..a600eb25bc3f9 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -23,7 +23,7 @@ export interface IDecodeStreamOptions { overwriteEncoding?(detectedEncoding: string): string; } -export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): TPromise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }> { +export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }> { if (!options.minBytesRequiredForDetection) { options.minBytesRequiredForDetection = options.guessEncoding ? AUTO_GUESS_BUFFER_MAX_LEN : NO_GUESS_BUFFER_MAX_LEN; @@ -33,7 +33,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions options.overwriteEncoding = detected => detected || UTF8; } - return new TPromise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }>((resolve, reject) => { + return new Promise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }>((resolve, reject) => { readable.pipe(new class extends Writable { private _decodeStream: NodeJS.ReadWriteStream; @@ -76,7 +76,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions _startDecodeStream(callback: Function): void { - this._decodeStreamConstruction = TPromise.as(detectEncodingFromBuffer({ + this._decodeStreamConstruction = Promise.resolve(detectEncodingFromBuffer({ buffer: Buffer.concat(this._buffer), bytesRead: this._bytesBuffered }, options.guessEncoding)).then(detected => { detected.encoding = options.overwriteEncoding(detected.encoding); diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 656270fe9b401..8ec483e2984b1 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -156,6 +156,25 @@ export enum FileType2 { SymbolicLink = 4, } +export class FileError extends Error { + + static readonly EEXIST = new FileError('EEXIST'); + static readonly ENOENT = new FileError('ENOENT'); + static readonly ENOTDIR = new FileError('ENOTDIR'); + static readonly EISDIR = new FileError('EISDIR'); + + constructor(readonly code: string) { + super(code); + } +} + +export enum FileOpenFlags { + Read = 0b0001, + Write = 0b0010, + Create = 0b0100, + Exclusive = 0b1000 +} + export interface IStat { mtime: number; size: number; @@ -173,13 +192,13 @@ export interface IFileSystemProviderBase { export interface ISimpleReadWriteProvider { _type: 'simple'; - readFile(resource: URI): TPromise; - writeFile(resource: URI, content: Uint8Array): TPromise; + readFile(resource: URI, opts: { flags: FileOpenFlags }): TPromise; + writeFile(resource: URI, content: Uint8Array, opts: { flags: FileOpenFlags }): TPromise; } export interface IReadWriteProvider { _type: 'chunked'; - open(resource: URI, options: { mode: string }): TPromise; + open(resource: URI, opts: { flags: FileOpenFlags }): TPromise; close(fd: number): TPromise; read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise; write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d8a89e60210a6..e9a1d6d7d7df9 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -223,6 +223,18 @@ declare module 'vscode' { // create(resource: Uri): Thenable; } + export class FileError extends Error { + + static readonly EEXIST: FileError; + static readonly ENOENT: FileError; + static readonly ENOTDIR: FileError; + static readonly EISDIR: FileError; + + readonly code: string; + + constructor(code: string, message?: string); + } + export enum FileChangeType2 { Changed = 1, Created = 2, @@ -246,12 +258,18 @@ declare module 'vscode' { size: number; } + export enum FileOpenFlags { + Read = 0b0001, + Write = 0b0010, + Create = 0b0100, + Exclusive = 0b1000 + } // todo@joh discover files etc // todo@joh add open/close calls? export interface FileSystemProvider2 { - _version: 4; + _version: 5; /** * An event to signal that a resource has been created, changed, or deleted. @@ -284,7 +302,7 @@ declare module 'vscode' { * @param token A cancellation token. * @return A thenable that resolves to an array of bytes. */ - readFile(uri: Uri, token: CancellationToken): Uint8Array | Thenable; + readFile(uri: Uri, options: { flags: FileOpenFlags }, token: CancellationToken): Uint8Array | Thenable; /** * Write data to a file, replacing its entire contents. @@ -293,7 +311,7 @@ declare module 'vscode' { * @param content The new content of the file. * @param token A cancellation token. */ - writeFile(uri: Uri, content: Uint8Array, token: CancellationToken): void | Thenable; + writeFile(uri: Uri, content: Uint8Array, options: { flags: FileOpenFlags }, token: CancellationToken): void | Thenable; /** * Rename a file or folder. diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts index d35ed439da424..fc951f96f6d2d 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts @@ -7,7 +7,7 @@ import URI, { UriComponents } from 'vs/base/common/uri'; import { TPromise, PPromise } from 'vs/base/common/winjs.base'; import { ExtHostContext, MainContext, IExtHostContext, MainThreadFileSystemShape, ExtHostFileSystemShape, IFileChangeDto } from '../node/extHost.protocol'; -import { IFileService, IStat, IFileChange, ISimpleReadWriteProvider, IFileSystemProviderBase } from 'vs/platform/files/common/files'; +import { IFileService, IStat, IFileChange, ISimpleReadWriteProvider, IFileSystemProviderBase, FileOpenFlags } from 'vs/platform/files/common/files'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; @@ -97,16 +97,16 @@ class RemoteFileSystemProvider implements ISimpleReadWriteProvider, IFileSystemP stat(resource: URI): TPromise { return this._proxy.$stat(this._handle, resource); } - readFile(resource: URI): TPromise { - return this._proxy.$readFile(this._handle, resource).then(encoded => { + readFile(resource: URI, opts: { flags: FileOpenFlags }): TPromise { + return this._proxy.$readFile(this._handle, resource, opts.flags).then(encoded => { return Buffer.from(encoded, 'base64'); }); } - writeFile(resource: URI, content: Uint8Array): TPromise { + writeFile(resource: URI, content: Uint8Array, opts: { flags: FileOpenFlags }): TPromise { let encoded = Buffer.isBuffer(content) ? content.toString('base64') : Buffer.from(content.buffer, content.byteOffset, content.byteLength).toString('base64'); - return this._proxy.$writeFile(this._handle, resource, encoded); + return this._proxy.$writeFile(this._handle, resource, encoded, opts.flags); } delete(resource: URI): TPromise { return this._proxy.$delete(this._handle, resource); diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 261de713e10a5..dc3145effd14e 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -58,6 +58,7 @@ import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { OverviewRulerLane } from 'vs/editor/common/model'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { ExtHostWebviews } from 'vs/workbench/api/node/extHostWebview'; +import * as files from 'vs/platform/files/common/files'; export interface IExtensionApiFactory { (extension: IExtensionDescription): typeof vscode; @@ -706,6 +707,8 @@ export function createApiFactory( FileType: extHostTypes.FileType, FileChangeType2: extHostTypes.FileChangeType2, FileType2: extHostTypes.FileType2, + FileOpenFlags: files.FileOpenFlags, + FileError: files.FileError, FoldingRangeList: extHostTypes.FoldingRangeList, FoldingRange: extHostTypes.FoldingRange, FoldingRangeType: extHostTypes.FoldingRangeType diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index d9ed0822a6746..14a3d33bd9468 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -42,7 +42,7 @@ import { ITreeItem } from 'vs/workbench/common/views'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { SerializedError } from 'vs/base/common/errors'; -import { IStat, FileChangeType } from 'vs/platform/files/common/files'; +import { IStat, FileChangeType, FileOpenFlags } from 'vs/platform/files/common/files'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { CommentRule, CharacterPair, EnterAction } from 'vs/editor/common/modes/languageConfiguration'; import { ISingleEditOperation } from 'vs/editor/common/model'; @@ -567,8 +567,8 @@ export interface ExtHostWorkspaceShape { export interface ExtHostFileSystemShape { $stat(handle: number, resource: UriComponents): TPromise; - $readFile(handle: number, resource: UriComponents): TPromise; - $writeFile(handle: number, resource: UriComponents, base64Encoded: string): TPromise; + $readFile(handle: number, resource: UriComponents, flags: FileOpenFlags): TPromise; + $writeFile(handle: number, resource: UriComponents, base64Encoded: string, flags: FileOpenFlags): TPromise; $move(handle: number, resource: UriComponents, target: UriComponents): TPromise; $mkdir(handle: number, resource: UriComponents): TPromise; diff --git a/src/vs/workbench/api/node/extHostFileSystem.ts b/src/vs/workbench/api/node/extHostFileSystem.ts index de9247046b7c5..0bfe4dec77957 100644 --- a/src/vs/workbench/api/node/extHostFileSystem.ts +++ b/src/vs/workbench/api/node/extHostFileSystem.ts @@ -57,10 +57,9 @@ class FsLinkProvider implements vscode.DocumentLinkProvider { } } - class FileSystemProviderShim implements vscode.FileSystemProvider2 { - _version: 4; + _version: 5; onDidChange: vscode.Event; @@ -154,7 +153,8 @@ class FileSystemProviderShim implements vscode.FileSystemProvider2 { return Buffer.concat(chunks); }); } - writeFile(resource: vscode.Uri, content: Uint8Array): Thenable { + writeFile(resource: vscode.Uri, content: Uint8Array, options: { flags: vscode.FileOpenFlags }): Thenable { + // if (options.flags & (files.FileOpenFlags.Exclusive) ) return this._delegate.write(resource, content); } } @@ -174,7 +174,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { } registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, newProvider: vscode.FileSystemProvider2) { - if (newProvider && newProvider._version === 4) { + if (newProvider && newProvider._version === 5) { return this._doRegisterFileSystemProvider(scheme, newProvider); } else if (provider) { return this._doRegisterFileSystemProvider(scheme, new FileSystemProviderShim(provider)); @@ -240,15 +240,15 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { $readdir(handle: number, resource: UriComponents): TPromise<[string, files.IStat][], any> { return asWinJsPromise(token => this._fsProvider.get(handle).readDirectory(URI.revive(resource), token)); } - $readFile(handle: number, resource: UriComponents): TPromise { + $readFile(handle: number, resource: UriComponents, flags: files.FileOpenFlags): TPromise { return asWinJsPromise(token => { - return this._fsProvider.get(handle).readFile(URI.revive(resource), token); + return this._fsProvider.get(handle).readFile(URI.revive(resource), { flags }, token); }).then(data => { return Buffer.isBuffer(data) ? data.toString('base64') : Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('base64'); }); } - $writeFile(handle: number, resource: UriComponents, base64Content: string): TPromise { - return asWinJsPromise(token => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), token)); + $writeFile(handle: number, resource: UriComponents, base64Content: string, flags: files.FileOpenFlags): TPromise { + return asWinJsPromise(token => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), { flags }, token)); } $delete(handle: number, resource: UriComponents): TPromise { return asWinJsPromise(token => this._fsProvider.get(handle).delete(URI.revive(resource), token)); diff --git a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts index 738ff3a1c87d2..9ea7b08edbb08 100644 --- a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts +++ b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts @@ -6,11 +6,11 @@ import URI from 'vs/base/common/uri'; import { FileService } from 'vs/workbench/services/files/electron-browser/fileService'; -import { IContent, IStreamContent, IFileStat, IResolveContentOptions, IUpdateContentOptions, IResolveFileOptions, IResolveFileResult, FileOperationEvent, FileOperation, IFileSystemProvider, IStat, FileType2, FileChangesEvent, ICreateFileOptions, FileOperationError, FileOperationResult, ITextSnapshot, StringSnapshot } from 'vs/platform/files/common/files'; +import { IContent, IStreamContent, IFileStat, IResolveContentOptions, IUpdateContentOptions, IResolveFileOptions, IResolveFileResult, FileOperationEvent, FileOperation, IFileSystemProvider, IStat, FileType2, FileChangesEvent, ICreateFileOptions, FileOperationError, FileOperationResult, ITextSnapshot, StringSnapshot, FileOpenFlags } from 'vs/platform/files/common/files'; import { TPromise } from 'vs/base/common/winjs.base'; import { posix } from 'path'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { isFalsyOrEmpty, distinct } from 'vs/base/common/arrays'; +import { isFalsyOrEmpty, distinct, flatten } from 'vs/base/common/arrays'; import { Schemas } from 'vs/base/common/network'; import { toDecodeStream, IDecodeStreamOptions, decodeStream } from 'vs/base/node/encoding'; import { TernarySearchTree } from 'vs/base/common/map'; @@ -186,9 +186,7 @@ export class RemoteFileService extends FileService { promises.push(this._doResolveFiles(group)); } } - return TPromise.join(promises).then(data => { - return [].concat(...data); - }); + return TPromise.join(promises).then(data => flatten(data)); } private _doResolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): TPromise { @@ -254,7 +252,7 @@ export class RemoteFileService extends FileService { } }; - const readable = createReadableOfProvider(provider, resource, options.position || 0); + const readable = createReadableOfProvider(provider, resource, options.position || 0, FileOpenFlags.Read); return toDecodeStream(readable, decodeStreamOpts).then(data => { @@ -286,20 +284,22 @@ export class RemoteFileService extends FileService { return super.createFile(resource, content, options); } else { return this._withProvider(resource).then(provider => { - let prepare = options && !options.overwrite - ? this.existsFile(resource) - : TPromise.as(false); + let flags = FileOpenFlags.Write | FileOpenFlags.Create; + if (options && options.overwrite === false) { + flags += FileOpenFlags.Exclusive; + } - return prepare.then(exists => { - if (exists && options && !options.overwrite) { - return TPromise.wrapError(new FileOperationError('EEXIST', FileOperationResult.FILE_MODIFIED_SINCE, options)); - } - return this._writeFile(provider, resource, content || '', {}); - }).then(fileStat => { - this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat)); - return fileStat; - }); + const encoding = this.encoding.getWriteEncoding(resource); + return this._writeFile(provider, resource, new StringSnapshot(content), { encoding }, flags); + + }).then(fileStat => { + this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat)); + return fileStat; + }, err => { + + // return TPromise.wrapError(new FileOperationError('EEXIST', FileOperationResult.FILE_MODIFIED_SINCE, options)); + throw err; }); } } @@ -309,21 +309,17 @@ export class RemoteFileService extends FileService { return super.updateContent(resource, value, options); } else { return this._withProvider(resource).then(provider => { - return this._writeFile(provider, resource, value, options || {}); + const snapshot = typeof value === 'string' ? new StringSnapshot(value) : value; + return this._writeFile(provider, resource, snapshot, options || {}, FileOpenFlags.Write); }); } } - private _writeFile(provider: IFileSystemProvider, resource: URI, content: string | ITextSnapshot, options: IUpdateContentOptions): TPromise { - - const snapshot = typeof content === 'string' ? new StringSnapshot(content) : content; + private _writeFile(provider: IFileSystemProvider, resource: URI, snapshot: ITextSnapshot, options: IUpdateContentOptions, fags: FileOpenFlags): TPromise { const readable = createReadableOfSnapshot(snapshot); - const encoding = this.encoding.getWriteEncoding(resource, options.encoding); const decoder = decodeStream(encoding); - - const target = createWritableOfProvider(provider, resource); - + const target = createWritableOfProvider(provider, resource, FileOpenFlags.Write); return new TPromise((resolve, reject) => { readable.pipe(decoder).pipe(target); target.once('error', err => reject(err)); @@ -438,7 +434,12 @@ export class RemoteFileService extends FileService { // https://github.com/Microsoft/vscode/issues/41543 return this.resolveContent(source, { acceptTextOnly: true }).then(content => { return this._withProvider(target).then(provider => { - return this._writeFile(provider, target, content.value, { encoding: content.encoding }).then(fileStat => { + return this._writeFile( + provider, target, + new StringSnapshot(content.value), + { encoding: content.encoding }, + FileOpenFlags.Create | FileOpenFlags.Write + ).then(fileStat => { this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat)); return fileStat; }); diff --git a/src/vs/workbench/services/files/electron-browser/streams.ts b/src/vs/workbench/services/files/electron-browser/streams.ts index 08bf5ca41e131..4af45b1711997 100644 --- a/src/vs/workbench/services/files/electron-browser/streams.ts +++ b/src/vs/workbench/services/files/electron-browser/streams.ts @@ -7,16 +7,16 @@ import { Readable, Writable } from 'stream'; import { UTF8 } from 'vs/base/node/encoding'; import URI from 'vs/base/common/uri'; -import { IFileSystemProvider, ITextSnapshot, ISimpleReadWriteProvider, IReadWriteProvider } from 'vs/platform/files/common/files'; +import { IFileSystemProvider, ITextSnapshot, ISimpleReadWriteProvider, IReadWriteProvider, FileOpenFlags } from 'vs/platform/files/common/files'; -export function createWritableOfProvider(provider: IFileSystemProvider, resource: URI): Writable { +export function createWritableOfProvider(provider: IFileSystemProvider, resource: URI, flags: FileOpenFlags): Writable { switch (provider._type) { - case 'simple': return createSimpleWritable(provider, resource); - case 'chunked': return createWritable(provider, resource); + case 'simple': return createSimpleWritable(provider, resource, flags); + case 'chunked': return createWritable(provider, resource, flags); } } -function createSimpleWritable(provider: ISimpleReadWriteProvider, resource: URI): Writable { +function createSimpleWritable(provider: ISimpleReadWriteProvider, resource: URI, flags: FileOpenFlags): Writable { return new class extends Writable { _chunks: Buffer[] = []; constructor(opts?) { @@ -28,7 +28,7 @@ function createSimpleWritable(provider: ISimpleReadWriteProvider, resource: URI) } end() { // todo@joh - end might have another chunk... - provider.writeFile(resource, Buffer.concat(this._chunks)).then(_ => { + provider.writeFile(resource, Buffer.concat(this._chunks), { flags }).then(_ => { super.end(); }, err => { this.emit('error', err); @@ -37,7 +37,7 @@ function createSimpleWritable(provider: ISimpleReadWriteProvider, resource: URI) }; } -function createWritable(provider: IReadWriteProvider, resource: URI): Writable { +function createWritable(provider: IReadWriteProvider, resource: URI, flags: FileOpenFlags): Writable { return new class extends Writable { _fd: number; _pos: number; @@ -47,7 +47,7 @@ function createWritable(provider: IReadWriteProvider, resource: URI): Writable { async _write(chunk: Buffer, encoding, callback: Function) { try { if (typeof this._fd !== 'number') { - this._fd = await provider.open(resource, { mode: 'w+' }); + this._fd = await provider.open(resource, { flags }); } let bytesWritten = await provider.write(this._fd, this._pos, chunk, 0, chunk.length); this._pos += bytesWritten; @@ -66,14 +66,14 @@ function createWritable(provider: IReadWriteProvider, resource: URI): Writable { }; } -export function createReadableOfProvider(provider: IFileSystemProvider, resource: URI, position: number): Readable { +export function createReadableOfProvider(provider: IFileSystemProvider, resource: URI, position: number, flags: FileOpenFlags): Readable { switch (provider._type) { - case 'simple': return createSimpleReadable(provider, resource, position); - case 'chunked': return createReadable(provider, resource, position); + case 'simple': return createSimpleReadable(provider, resource, position, flags); + case 'chunked': return createReadable(provider, resource, position, flags); } } -function createReadable(provider: IReadWriteProvider, resource: URI, position: number): Readable { +function createReadable(provider: IReadWriteProvider, resource: URI, position: number, flags: FileOpenFlags): Readable { return new class extends Readable { _fd: number; _pos: number = position; @@ -89,7 +89,7 @@ function createReadable(provider: IReadWriteProvider, resource: URI, position: n this._reading = true; try { if (typeof this._fd !== 'number') { - this._fd = await provider.open(resource, { mode: 'r' }); + this._fd = await provider.open(resource, { flags }); } let buffer = Buffer.allocUnsafe(64 * 1024); while (this._reading) { @@ -117,14 +117,14 @@ function createReadable(provider: IReadWriteProvider, resource: URI, position: n }; } -function createSimpleReadable(provider: ISimpleReadWriteProvider, resource: URI, position: number): Readable { +function createSimpleReadable(provider: ISimpleReadWriteProvider, resource: URI, position: number, flags: FileOpenFlags): Readable { return new class extends Readable { _readOperation: Thenable; _read(size?: number): void { if (this._readOperation) { return; } - this._readOperation = provider.readFile(resource).then(data => { + this._readOperation = provider.readFile(resource, { flags }).then(data => { this.push(data.slice(position)); this.push(null); }, err => {