Thwacknya adalah:
README ini sedang dalam proses. Anda juga dapat mengajukan pertanyaan kepada saya di Twitter.
$ npm i thwack
atau
$ yarn add thwack
Axios sangat bagus ketika dirilis pada masa lalu. Ini memberi kami pembungkus berbasis janji di sekitar XMLHttpRequest
, yang sulit digunakan. Namun hal tersebut sudah lama terjadi dan zaman telah berubah — browser kini menjadi lebih pintar. Mungkin sudah waktunya solusi pengambilan data Anda mengikuti?
Thwack dibangun dari awal dengan mempertimbangkan browser modern. Karena itu, ia tidak memiliki bagasi seperti yang dimiliki Axios. Axios berbobot sekitar ~5k gzip. Thwack, di sisi lain, ramping ~1,5k.
Keduanya mendukung API yang sama, namun terdapat beberapa perbedaan — terutama pada options
— namun secara umum, keduanya dapat digunakan secara bergantian untuk banyak aplikasi.
Thwack tidak mencoba menyelesaikan setiap masalah, seperti yang dilakukan Axios, namun memberikan solusi untuk 98% dari apa yang benar-benar dibutuhkan pengguna. Inilah yang membuat Thwack memiliki jejak yang sangat ringan.
Gores itu. Thwack memberikan tingkat kekuatan yang sama dengan Axios dengan ukuran yang jauh lebih kecil. Dan sistem acara berbasis janji Thwack lebih mudah digunakan.
Metode berikut tersedia di semua instans Thwack.
thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.request(options: ThwackOptions): Promise<ThwackResponse>
thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.create(options: ThwackOptions): ThwackInstance;
Metode create
membuat (da!) instance anak baru dari instance Thwack saat ini dengan options
yang diberikan.
thwack.getUri(options: ThwackOptions): string;
Resolusi URL Thwacks sesuai dengan RFC-3986. Axios tidak. Ini didukung oleh @thwack/resolve
.
Thwack mendukung jenis acara berikut: request
, response
, data
, dan error
.
Untuk informasi lebih lanjut tentang sistem acara Thwack, lihat acara Thwack di bawah.
thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;
thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;
Thwack memiliki fungsi pembantu berikut untuk membuat permintaan secara bersamaan. Sebagian besar ditujukan untuk kompatibilitas Axios. Lihat bagian "Cara" di bawah untuk contoh penggunaan.
thwack.all(Promise<ThwackResponse>[])
thwack.spread(callback<results>)
Argumen options
memiliki properti berikut.
url
Ini bisa berupa URL yang sepenuhnya memenuhi syarat atau URL relatif.
baseURL
Menentukan URL dasar yang akan digunakan untuk membuat URL yang sepenuhnya memenuhi syarat dari url
di atas. Harus berupa URL absolut atau undefined
. Defaultnya adalah origin
+ pathname
halaman web saat ini jika dijalankan di browser atau undefined
di Node atau React Native.
Misalnya, jika Anda melakukan ini:
thwack ( 'foo' , {
baseURL : 'http://example.com' ,
} ) ;
URL yang diambil akan menjadi:
http://example.com/foo
method
Sebuah string yang berisi salah satu metode HTTP berikut: get
, post
, put
, patch
, delete
, atau head
.
data
Jika method
adalah post
, put
, atau patch
, ini adalah data yang akan digunakan untuk membuat isi permintaan.
headers
Di sinilah Anda dapat menempatkan header permintaan HTTP opsional apa pun. Setiap header yang Anda tentukan di sini digabungkan dengan nilai header instance apa pun.
Misalnya, jika kita menyetel instance Thwack seperti ini:
const api = thwack . create ( {
headers : {
'x-app-name' : 'My Awesome App' ,
} ,
} ) ;
Kemudian nanti, saat Anda menggunakan instance tersebut, Anda melakukan panggilan seperti ini:
const { data } = await api . get ( 'foo' , {
headers : {
'some-other-header' : 'My Awesome App' ,
} ,
} ) ;
Header yang akan dikirimkan adalah:
x-app-name: My Awesome App
some-other-header': 'My Awesome App'
defaults
Hal ini memungkinkan Anda membaca/mengatur opsi default untuk instance ini dan, pada dasarnya, semua instance turunan.
Contoh:
thwack . defaults . baseURL = 'https://example.com/api' ;
Misalnya, defaults
adalah objek yang sama yang diteruskan ke create
. Misalnya, berikut ini akan menampilkan "https://example.com/api".
const instance = thwack . create ( {
baseURL : 'https://example.com/api' ,
} ) ;
console . log ( instance . defaults . baseURL ) ;
Perhatikan juga bahwa pengaturan defaults
pada sebuah instance (atau bahkan meneruskan options
) ke sebuah instance TIDAK mempengaruhi induknya. Jadi untuk contoh berikut, thwack.defaults.baseURL
akan tetap menjadi "https://api1.example.net/".
thwack . defaults . baseURL = 'https://api1.example.net/' ;
const instance = thwack . create ( ) ;
instance . defaults . baseURL = 'https://example.com/api' ;
console . log ( thwack . defaults . baseURL ) ;
params
Ini adalah objek opsional yang berisi pasangan kunci/nilai yang akan digunakan untuk membuat URL pengambilan. Apakah ada segmen :key
dari baseURL
atau url
, segmen tersebut akan diganti dengan nilai kunci yang cocok. Misalnya, jika Anda melakukan ini:
thwack ( 'orders/:id' , {
params : { id : 123 } ,
baseURL : 'http://example.com' ,
} ) ;
URL yang diambil akan menjadi:
http://example.com/orders/123
Jika Anda tidak menentukan :name
, atau ada lebih banyak param
daripada :name
, maka kunci/nilai yang tersisa akan ditetapkan sebagai parameter pencarian (yaitu ?key=value
).
maxDepth
Tingkat maksimum permintaan rekursif yang dapat dibuat dalam panggilan sebelum Thwack menimbulkan kesalahan. Ini digunakan untuk mencegah panggilan balik peristiwa menyebabkan perulangan rekursif, Ini jika mengeluarkan request
lain tanpa perlindungan yang tepat. Bawaan = 3.
responseType
Secara default, Thwack akan secara otomatis menentukan cara mendekode data respons berdasarkan nilai header respons content-type
. Namun, jika server merespons dengan nilai yang salah, Anda dapat mengganti parser dengan menyetel responseType
. Nilai yang valid adalah arraybuffer
, document
(yaitu formdata
), json
, text
, stream
, dan blob
. Defaultnya otomatis.
Apa yang dikembalikan oleh Thwack ditentukan oleh tabel berikut. Kolom "metode pengambilan" adalah yang diselesaikan di data
. Jika Anda tidak menentukan responseType
, Thwack akan secara otomatis menentukan metode pengambilan berdasarkan content-type
dan tabel responseParserMap
(lihat di bawah).
Tipe Konten | responseType | metode fetch |
---|---|---|
application/json | json | response.json() |
multipart/form-data | formdata | response.formData() |
text/event-stream | stream | meneruskan response.body sebagai data tanpa pemrosesan |
blob | response.blob() | |
arraybuffer | response.arrayBuffer() | |
*/* | text | response.text() |
Catatan:
stream
saat ini tidak didukung di React Native karena #27741
responseParserMap
Cara lain yang berguna untuk menentukan parser respon mana yang akan digunakan adalah dengan responseParserMap
. Ini memungkinkan Anda mengatur pemetaan antara content-type
yang dihasilkan dari header respons dan tipe parser.
Thwack menggunakan peta berikut sebagai default, yang memungkinkan decoding json
dan formdata
. Jika tidak ada yang cocok, parser respons defaultnya adalah text
. Anda dapat menentukan default dengan mengatur kunci */*
khusus.
{
"application/json" : " json " ,
"multipart/form-data" : " formdata " ,
"*/*" : " text "
} ;
Nilai apa pun yang Anda tentukan di responseParserMap
digabungkan ke dalam peta default. Artinya, Anda dapat mengganti nilai default dan/atau menambahkan nilai baru.
Katakanlah, misalnya, Anda ingin mengunduh gambar ke dalam blob. Anda dapat mengatur baseURL
ke titik akhir API dan responseParserMap
yang akan mengunduh gambar jenis apa pun sebagai blob, namun tetap mengizinkan unduhan json
(karena ini adalah default untuk content-type: application/json
).
import thwack from 'thwack' ;
thwack . defaults . responseParserMap = { 'image/*' : 'blob' } ;
URL apa pun yang Anda unduh dengan tipe konten image/*
(misalnya image/jpeg
, image/png
, dll) akan diurai dengan parser blob
.
const getBlobUrl = async ( url ) => {
const blob = ( await thwack . get ( url ) ) . data ;
const objectURL = URL . createObjectURL ( blob ) ;
return objectURL ;
} ;
Lihat contoh ini berjalan di CodeSandbox.
Perhatikan bahwa Anda dapat menggunakan teknik ini untuk hal lain selain gambar.
Seperti yang Anda lihat, menggunakan responseParserMap
adalah cara terbaik untuk menghilangkan kebutuhan untuk mengatur responseType
untuk panggilan Thwack yang berbeda.
validateStatus
Fungsi opsional ini digunakan untuk menentukan kode status apa yang digunakan Thwack untuk mengembalikan janji atau lemparan. Itu melewati status
respons. Jika fungsi ini mengembalikan kebenaran, janji terselesaikan, jika tidak, janji akan ditolak.
Fungsi default muncul untuk status apa pun yang tidak ada di 2xx (yaitu 200-299)
paramsSerializer
Ini adalah fungsi opsional yang akan dipanggil Thwack untuk membuat serialisasi params
. Misalnya, jika diberi objek {a:1, b:2, foo: 'bar'}
, objek tersebut harus diserialkan ke string a=1&b=2&foo=bar
.
Bagi kebanyakan orang, serializer default seharusnya berfungsi dengan baik. Ini terutama untuk kompatibilitas edge case dan Axios.
Perhatikan bahwa serializer default mengurutkan parameter berdasarkan abjad, yang merupakan praktik yang baik untuk diikuti. Namun, jika ini tidak berhasil untuk situasi Anda, Anda dapat memutar serializer Anda sendiri.
resolver
Ini adalah fungsi yang dapat Anda berikan untuk mengganti perilaku penyelesai default. resolver
mengambil dua argumen: url
dan baseURL
yang harus tidak ditentukan, atau URL absolut. Seharusnya tidak ada alasan bagi Anda untuk mengganti penyelesai, tetapi ini adalah jalan keluar jika diperlukan.
status
number
yang mewakili 3 digit kode status HTTP yang diterima.
ok
boolean
yang disetel ke true adalah kode status
dalam rentang 2xx (yaitu sukses). Nilai ini tidak dipengaruhi oleh validateStatus
.
statusText
Sebuah string
yang mewakili teks kode status
. Anda harus menggunakan kode status
(atau ok
) dalam logika program apa pun.
headers
Objek kunci/nilai dengan header HTTP yang dikembalikan. Setiap header duplikat akan digabungkan menjadi satu header yang dipisahkan dengan titik koma.
data
Ini akan menyimpan isi respons HTTP yang dikembalikan setelah dialirkan dan dikonversi. Satu-satunya pengecualian adalah jika Anda menggunakan responseType
dari stream
, dalam hal ini data
disetel langsung ke elemen body
.
Jika ThwackResponseError
dilempar, data
akan menjadi representasi teks biasa dari isi respons.
options
Objek options
lengkap yang memproses permintaan. options
ini akan sepenuhnya digabungkan dengan instance induk mana pun, serta dengan defaults
.
response
Objek Response
HTTP lengkap seperti yang dikembalikan oleh fetch
atau response
dari panggilan balik peristiwa sintetis.
Jika respon dari permintaan Thwack menghasilkan kode status
non-2xx (misalnya 404 Not Found) maka ThwackResponseError
akan dilempar.
Catatan: Ada kemungkinan bahwa jenis kesalahan lain dapat terjadi (misalnya callback pendengar peristiwa yang buruk), jadi praktik terbaiknya adalah menginterogasi kesalahan yang tertangkap untuk melihat apakah kesalahan tersebut bertipe
ThwackResponseError
.
try {
const { data } = await thwack . get ( someUrl )
} catch ( ex ) {
if ( ex instanceof thwack . ThwackResponseError )
const { status , message } = ex ;
console . log ( `Thwack status ${ status } : ${ message } ` ) ;
} else {
throw ex ; // If not, rethrow the error
}
}
ThwackResponseError
memiliki semua properti dari JavaScript Error
normal ditambah properti thwackResponse
dengan properti yang sama dengan status sukses.
Instance yang dibuat di Thwack didasarkan pada instance induknya. Opsi default orang tua diturunkan melalui instance. Ini berguna untuk menyiapkan opsi di induk yang dapat memengaruhi turunan, seperti baseURL
,
Sebaliknya, orang tua dapat menggunakan addEventListener
untuk memantau anak-anak mereka (lihat Cara mencatat setiap panggilan API di bawah untuk contohnya).
Dikombinasikan dengan instance, sistem event Thwack inilah yang membuat Thwack sangat kuat. Dengan itu, Anda dapat mendengarkan berbagai acara.
Berikut adalah alur acara untuk semua acara. SEPERTI yang Anda lihat, ada kemungkinan kode Anda masuk ke loop tanpa akhir, jika panggilan balik Anda secara membabi buta mengeluarkan request()
tanpa memeriksa apakah sudah melakukannya, jadi berhati-hatilah.
request
Setiap kali bagian mana pun dari aplikasi memanggil salah satu metode pengambilan data, peristiwa request
akan dipicu. Setiap pendengar akan mendapatkan objek ThwackRequestEvent
yang memiliki options
panggilan di event.options
. Pemroses peristiwa ini dapat melakukan sesuatu yang sederhana seperti (mencatat peristiwa) atau rumit seperti mencegah permintaan dan mengembalikan respons dengan (data tiruan)
// callback will be called for every request made in Thwack
thwack . addEventListener ( 'request' , callback ) ;
Perhatikan bahwa callback bisa bersifat
async
sehingga Anda dapat menunda Thwack sehingga Anda dapat, misalnya, keluar dan mengambil data dari URL yang berbeda sebelum melanjutkan.
response
Peristiwa ini dipicu setelah header HTTP diterima, tetapi sebelum isi dialirkan dan diurai. Pendengar akan menerima objek ThwackResponseEvent
dengan kunci thwackResponse
yang disetel ke responsnya.
data
Acara ini dipecat setelah tubuh dialirkan dan diurai. Ini diaktifkan hanya jika pengambilan mengembalikan kode status 2xx. Pendengar akan menerima objek ThwackDataEvent
dengan kunci thwackResponse
yang disetel ke responsnya.
error
Acara ini dipecat setelah tubuh dialirkan dan diurai. Ini diaktifkan jika pengambilan mengembalikan kode status non-2xx. Pendengar akan menerima objek ThwackErrorEvent
dengan kunci thwackResponse
yang disetel ke responsnya.
Thwack akan berfungsi di NodeJS, tetapi memerlukan polyfill untuk window.fetch
. Untungnya, ada polyfill luar biasa bernama node-fetch
yang dapat Anda gunakan.
Jika Anda menggunakan NodeJS versi 10, Anda juga memerlukan polyfill untuk Array#flat
dan Object#fromEntries
. NodeJS versi 11+ memiliki metode ini dan tidak memerlukan polyfill.
Anda dapat menyediakan polyfill ini sendiri, atau menggunakan salah satu dari impor praktis berikut ini. Jika Anda menjalankan NodeJS 11+, gunakan:
import thwack from 'thwack/node' ; // NodeJS version 12+
Jika Anda menjalankan NodeJS 10, gunakan:
import thwack from 'thwack/node10' ; // NodeJS version 10
Jika Anda ingin menyediakan polyfill ini sendiri, maka untuk menggunakan Thwack, Anda harus mengimpor dari thwack/core
dan menyetel fetch
sebagai default untuk fetch
.
import thwack from 'thwack/code' ;
thwack . defaults . fetch = global . fetch ;
Ini harus dilakukan di kode startup aplikasi Anda, biasanya index.js
.
Catatan:
responseType
blob
tidak didukung di NodeJS.
Thwack kompatibel dengan React Native dan tidak memerlukan polyfill tambahan. Lihat di bawah untuk contoh aplikasi yang ditulis dalam React Native.
Catatan: React Native tidak mendukung
stream
karena #27741
Anda dapat menggunakan thwack.all()
dan thwack.spread()
untuk membuat permintaan secara bersamaan. Data kemudian disajikan ke panggilan balik Anda sebagai satu larik.
Di sini kami menampilkan informasi untuk dua pengguna GitHub.
function displayGitHubUsers ( ) {
return thwack
. all ( [
thwack . get ( 'https://api.github.com/users/donavon' ) ,
thwack . get ( 'https://api.github.com/users/revelcw' ) ,
] )
. then (
thwack . spread ( ( ... results ) => {
const output = results
. map (
( { data } ) => ` ${ data . login } has ${ data . public_repos } public repos`
)
. join ( 'n' ) ;
console . log ( output ) ;
} )
) ;
}
Perhatikan bahwa ini hanyalah fungsi pembantu. Jika Anda menggunakan async
/ await
Anda dapat menulis ini tanpa bantuan Thwack menggunakan Promise.all
.
async function displayGitHubUsers ( ) {
const results = await Promise . all ( [
thwack . get ( 'https://api.github.com/users/donavon' ) ,
thwack . get ( 'https://api.github.com/users/revelcw' ) ,
] ) ;
const output = results
. map ( ( { data } ) => ` ${ data . login } has ${ data . public_repos } public repos` )
. join ( 'n' ) ;
console . log ( output ) ;
}
Anda dapat melihatnya berjalan langsung di CodeSandbox.
(Demo terinspirasi oleh postingan blob ini di axios/fetch)
Gunakan AbortController
untuk membatalkan permintaan dengan meneruskan signal
di opsi thwack
.
Di browser, Anda dapat menggunakan AbortController bawaan.
import thwack from 'thwack' ;
const controller = new AbortController ( ) ;
const { signal } = controller ;
thwack ( url , { signal } ) . then ( handleResponse ) . catch ( handleError ) ;
controller . abort ( ) ;
Di NodeJS, Anda dapat menggunakan sesuatu seperti abort-controller.
import thwack from 'thwack' ;
import AbortController from 'abort-controller' ;
const controller = new AbortController ( ) ;
const { signal } = controller ;
thwack ( url , { signal } ) . then ( handleResponse ) . catch ( handleError ) ;
controller . abort ( ) ;
Jika Anda ingin melakukan beberapa tindakan pada pembatalan permintaan, Anda juga dapat mendengarkan acara abort
pada signal
:
signal . addEventListener ( 'abort' , handleAbort ) ;
Tambahkan addEventListener('request', callback)
dan catat setiap permintaan ke konsol.
import thwack from 'thwack' ;
thwack . addEventListener ( 'request' , ( event ) => {
console . log ( 'hitting URL' , thwack . getUri ( event . options ) ) ;
} ) ;
Jika Anda menggunakan React, berikut adalah Hook yang dapat Anda "gunakan" di Aplikasi Anda yang akan menghasilkan hal yang sama.
import { useEffect } from 'react' ;
import thwack from 'thwack' ;
const logUrl = ( event ) => {
const { options } = event ;
const fullyQualifiedUrl = thwack . getUri ( options ) ;
console . log ( `hitting ${ fullyQualifiedUrl } ` ) ;
} ;
const useThwackLogger = ( ) => {
useEffect ( ( ) => {
thwack . addEventListener ( 'request' , logUrl ) ;
return ( ) => thwack . removeEventListener ( 'request' , logUrl ) ;
} , [ ] ) ;
} ;
export default useThwackLogger ;
Berikut cuplikan kode cara menggunakannya.
const App = ( ) = {
useThwackLogger ( )
return (
< div >
...
</ div >
)
}
Katakanlah Anda memiliki aplikasi yang meminta beberapa data pengguna. Jika aplikasi mencapai URL tertentu (misalnya users
) dan menanyakan ID pengguna tertentu (misalnya 123
), Anda ingin mencegah permintaan tersebut mengenai server dan malah meniru hasilnya.
status
di ThwackResponse
defaultnya adalah 200, jadi kecuali Anda perlu meniru respons yang tidak OK, Anda hanya perlu mengembalikan data
.
thwack . addEventListener ( 'request' , async ( event ) => {
const { options } = event ;
if ( options . url === 'users' && options . params . id === 123 ) {
// tells Thwack to use the returned value instead of handling the event itself
event . preventDefault ( ) ;
// stop other listeners (if any) from further processing
event . stopPropagation ( ) ;
// because we called `preventDefault` above, the caller's request
// will be resolved to this `ThwackResponse` (defaults to status of 200 and ok)
return new thwack . ThwackResponse (
{
data : {
name : 'Fake Username' ,
email : '[email protected]' ,
} ,
} ,
options
) ;
}
} ) ;
Seringkali diinginkan untuk mengubah DTO (Data Transfer Object) menjadi sesuatu yang lebih mudah dikonsumsi oleh klien. Dalam contoh di bawah ini, kami mengubah DTO kompleks menjadi firstName
, lastName
, avatar
, dan email
. Elemen data lain yang dikembalikan dari panggilan API, namun tidak diperlukan oleh aplikasi, akan diabaikan.
Anda dapat melihat contoh konversi DTO, pencatatan, dan pengembalian data palsu di aplikasi contoh ini.
Anda dapat melihat kode sumber di CodeSandbox.
Dalam contoh ini, kita memiliki React Hook yang memuat gambar sebagai URL Blob. Ini menyimpan URL ke pemetaan URL Blob dalam penyimpanan sesi. Setelah dimuat, setiap penyegaran halaman akan langsung memuat gambar dari URL Blob.
const useBlobUrl = ( imageUrl ) => {
const [ objectURL , setObjectURL ] = useState ( '' ) ;
useEffect ( ( ) => {
let url = sessionStorage . getItem ( imageUrl ) ;
async function fetchData ( ) {
if ( ! url ) {
const { data } = await thwack . get ( imageUrl , {
responseType : 'blob' ,
} ) ;
url = URL . createObjectURL ( data ) ;
sessionStorage . setItem ( imageUrl , url ) ;
}
setObjectURL ( url ) ;
}
fetchData ( ) ;
} , [ imageUrl ] ) ;
return objectURL ;
} ;
Lihat contoh ini di CodeSandbox
Saat ini Anda memiliki titik akhir REST di https://api.example.com
. Misalkan Anda telah menerbitkan titik akhir REST baru ke URL yang berbeda dan ingin mulai merutekan 2% lalu lintas jaringan secara perlahan ke server baru ini.
Catatan: biasanya ini akan ditangani oleh penyeimbang beban Anda di back-end. Ini ditampilkan di sini untuk tujuan demonstrasi saja.
Kita dapat melakukannya dengan mengganti options.url
di pendengar acara permintaan sebagai berikut.
thwack . addEventListener ( 'request' , ( event ) => {
if ( Math . random ( ) >= 0.02 ) {
return ;
}
// the code will be executed for approximately 2% of the requests
const { options } = event ;
const oldUrl = thwack . getUri ( options ) ;
const url = new URL ( '' , oldUrl ) ;
url . origin = 'https://api2.example.com' ; // point the origin at the new servers
const newUrl = url . href ; // Get the fully qualified URL
event . options = { ... event . options , url : newUrl } ; // replace `options`]
} ) ;
Selain use-thwack
, menulis aplikasi pengambilan data untuk React Native sangatlah mudah.
Lihat seluruh aplikasi yang berjalan di Expo.
Thwack sangat terinspirasi oleh Axios. Terima kasih Matt!
Berlisensi di bawah MIT
Terima kasih kepada orang-orang hebat ini (kunci emoji):
Donavon Barat ? | Jeremy Tice | Yuraima Estevez | Jeremy Bargar | Brooke Scarlett Yalof | Karl Horky | Koji |
Tom Byrer | Ian Sutherland | Blake Yoder | Ryan Hinchey | Miro Dojkic | Santicevic |
Proyek ini mengikuti spesifikasi semua kontributor. Kontribusi apa pun diterima!