Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to

### Added

- Add optional error callbacks to `getJson` and `getHtml`.
- Expose `EngineParameters` type.
- Expose `InvalidArgumentError` error.

Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,20 @@ Get a JSON response based on search parameters.
**[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)**
search query parameters for the engine
- `callback` **fn?** optional callback
- `errorCallback` **fn?** optional callback invoked when the request fails

#### Examples

```javascript
// single call (async/await)
const json = await getJson({ engine: "google", api_key: API_KEY, q: "coffee" });

// single call (callback)
getJson({ engine: "google", api_key: API_KEY, q: "coffee" }, console.log);
// single call (callback with error handling)
getJson(
{ engine: "google", api_key: API_KEY, q: "coffee" },
console.log,
console.error,
);
```

### getHtml
Expand All @@ -223,15 +228,20 @@ Get a HTML response based on search parameters.
**[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)**
search query parameters for the engine
- `callback` **fn?** optional callback
- `errorCallback` **fn?** optional callback invoked when the request fails

#### Examples

```javascript
// async/await
const html = await getHtml({ engine: "google", api_key: API_KEY, q: "coffee" });

// callback
getHtml({ engine: "google", api_key: API_KEY, q: "coffee" }, console.log);
// callback with error handling
getHtml(
{ engine: "google", api_key: API_KEY, q: "coffee" },
console.log,
console.error,
);
```

### getJsonBySearchId
Expand Down
72 changes: 54 additions & 18 deletions src/serpapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,33 @@ const LOCATIONS_PATH = "/locations.json";
const SEARCH_PATH = "/search";
const SEARCH_ARCHIVE_PATH = `/searches`;

type ErrorCallback = (error: unknown) => void;

function observeError<T>(
promise: Promise<T>,
errorCallback?: ErrorCallback,
): Promise<T> {
if (errorCallback) void promise.catch(errorCallback);
return promise;
}

/**
* Get JSON response based on search parameters.
*
* @param {object} parameters Search query parameters for the engine. Refer to https://serpapi.com/search-api for parameter explanations.
* @param {fn=} callback Optional callback.
* @param {fn=} errorCallback Optional callback invoked when the request fails.
* @example
* // single call (async/await)
* const json = await getJson({ engine: "google", api_key: API_KEY, q: "coffee" });
*
* // single call (callback)
* getJson({ engine: "google", api_key: API_KEY, q: "coffee" }, console.log);
* // single call (callback with error handling)
* getJson({ engine: "google", api_key: API_KEY, q: "coffee" }, console.log, console.error);
*/
export function getJson(
parameters: EngineParameters,
callback?: (json: BaseResponse) => void,
errorCallback?: ErrorCallback,
): Promise<BaseResponse>;

/**
Expand All @@ -37,39 +49,50 @@ export function getJson(
* @param {string} engine Engine name. Refer to https://serpapi.com/search-api for valid engines.
* @param {object} parameters Search query parameters for the engine. Refer to https://serpapi.com/search-api for parameter explanations.
* @param {fn=} callback Optional callback.
* @param {fn=} errorCallback Optional callback invoked when the request fails.
* @example
* // single call (async/await)
* const json = await getJson("google", { api_key: API_KEY, q: "coffee" });
*
* // single call (callback)
* getJson("google", { api_key: API_KEY, q: "coffee" }, console.log);
* // single call (callback with error handling)
* getJson("google", { api_key: API_KEY, q: "coffee" }, console.log, console.error);
*/
export function getJson(
engine: string,
parameters: EngineParameters,
callback?: (json: BaseResponse) => void,
errorCallback?: ErrorCallback,
): Promise<BaseResponse>;

export function getJson(
...args:
| [parameters: EngineParameters, callback?: (json: BaseResponse) => void]
| [
parameters: EngineParameters,
callback?: (json: BaseResponse) => void,
errorCallback?: ErrorCallback,
]
| [
engine: string,
parameters: EngineParameters,
callback?: (json: BaseResponse) => void,
errorCallback?: ErrorCallback,
]
): Promise<BaseResponse> {
if (typeof args[0] === "string" && typeof args[1] === "object") {
const [engine, parameters, callback] = args;
const [engine, parameters, callback, errorCallback] = args;
const newParameters = { ...parameters, engine } as EngineParameters;
return _getJson(newParameters, callback);
return observeError(_getJson(newParameters, callback), errorCallback);
} else if (
typeof args[0] === "object" &&
typeof args[1] !== "object" &&
(typeof args[1] === "undefined" || typeof args[1] === "function")
) {
const [parameters, callback] = args;
return _getJson(parameters, callback);
const [parameters, callback, errorCallback] = args as [
EngineParameters,
((json: BaseResponse) => void)?,
ErrorCallback?,
];
return observeError(_getJson(parameters, callback), errorCallback);
} else {
throw new InvalidArgumentError();
}
Expand Down Expand Up @@ -100,16 +123,18 @@ async function _getJson(
*
* @param {object} parameters Search query parameters for the engine. Refer to https://serpapi.com/search-api for parameter explanations.
* @param {fn=} callback Optional callback.
* @param {fn=} errorCallback Optional callback invoked when the request fails.
* @example
* // async/await
* const html = await getHtml({ engine: "google", api_key: API_KEY, q: "coffee" });
*
* // callback
* getHtml({ engine: "google", api_key: API_KEY, q: "coffee" }, console.log);
* // callback with error handling
* getHtml({ engine: "google", api_key: API_KEY, q: "coffee" }, console.log, console.error);
*/
export function getHtml(
parameters: EngineParameters,
callback?: (html: string) => void,
errorCallback?: ErrorCallback,
): Promise<string>;

/**
Expand All @@ -118,39 +143,50 @@ export function getHtml(
* @param {string} engine Engine name. Refer to https://serpapi.com/search-api for valid engines.
* @param {object} parameters Search query parameters for the engine. Refer to https://serpapi.com/search-api for parameter explanations.
* @param {fn=} callback Optional callback.
* @param {fn=} errorCallback Optional callback invoked when the request fails.
* @example
* // async/await
* const html = await getHtml({ engine: "google", api_key: API_KEY, q: "coffee" });
*
* // callback
* getHtml({ engine: "google", api_key: API_KEY, q: "coffee" }, console.log);
* // callback with error handling
* getHtml("google", { api_key: API_KEY, q: "coffee" }, console.log, console.error);
*/
export function getHtml(
engine: string,
parameters: EngineParameters,
callback?: (html: string) => void,
errorCallback?: ErrorCallback,
): Promise<string>;

export function getHtml(
...args:
| [parameters: EngineParameters, callback?: (html: string) => void]
| [
parameters: EngineParameters,
callback?: (html: string) => void,
errorCallback?: ErrorCallback,
]
| [
engine: string,
parameters: EngineParameters,
callback?: (html: string) => void,
errorCallback?: ErrorCallback,
]
): Promise<string> {
if (typeof args[0] === "string" && typeof args[1] === "object") {
const [engine, parameters, callback] = args;
const [engine, parameters, callback, errorCallback] = args;
const newParameters = { ...parameters, engine } as EngineParameters;
return _getHtml(newParameters, callback);
return observeError(_getHtml(newParameters, callback), errorCallback);
} else if (
typeof args[0] === "object" &&
typeof args[1] !== "object" &&
(typeof args[1] === "undefined" || typeof args[1] === "function")
) {
const [parameters, callback] = args;
return _getHtml(parameters, callback);
const [parameters, callback, errorCallback] = args as [
EngineParameters,
((html: string) => void)?,
ErrorCallback?,
];
return observeError(_getHtml(parameters, callback), errorCallback);
} else {
throw new InvalidArgumentError();
}
Expand Down
122 changes: 122 additions & 0 deletions tests/serpapi_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,67 @@ describe(
},
);

it("error callback", async () => {
const requestError = new Error("request failed");
const executeStub = stub(
_internals,
"execute",
() => Promise.reject(requestError),
);
const successCallback = spy((_json: BaseResponse) => {});
const errorCallback = spy((_error: unknown) => {});

try {
await assertRejects(
() =>
getJson(
{
engine: "google",
q: "Paris",
api_key: "test_api_key",
},
successCallback,
errorCallback,
),
Error,
"request failed",
);
await assertRejects(
() =>
getJson(
"google",
{ q: "Paris", api_key: "test_api_key" },
successCallback,
errorCallback,
),
Error,
"request failed",
);
await new Promise<void>((done) => {
void getJson(
{
engine: "google",
q: "Paris",
api_key: "test_api_key",
},
successCallback,
(error) => {
errorCallback(error);
done();
},
);
});
} finally {
executeStub.restore();
}

assertSpyCalls(successCallback, 0);
assertSpyCalls(errorCallback, 3);
assertSpyCallArg(errorCallback, 0, 0, requestError);
assertSpyCallArg(errorCallback, 1, 0, requestError);
assertSpyCallArg(errorCallback, 2, 0, requestError);
});

it("rely on global config", async () => {
const executeSpy = spy(_internals, "execute");
config.api_key = "test_api_key";
Expand Down Expand Up @@ -545,6 +606,67 @@ describe(
},
);

it("error callback", async () => {
const requestError = new Error("request failed");
const executeStub = stub(
_internals,
"execute",
() => Promise.reject(requestError),
);
const successCallback = spy((_html: string) => {});
const errorCallback = spy((_error: unknown) => {});

try {
await assertRejects(
() =>
getHtml(
{
engine: "google",
q: "Paris",
api_key: "test_api_key",
},
successCallback,
errorCallback,
),
Error,
"request failed",
);
await assertRejects(
() =>
getHtml(
"google",
{ q: "Paris", api_key: "test_api_key" },
successCallback,
errorCallback,
),
Error,
"request failed",
);
await new Promise<void>((done) => {
void getHtml(
{
engine: "google",
q: "Paris",
api_key: "test_api_key",
},
successCallback,
(error) => {
errorCallback(error);
done();
},
);
});
} finally {
executeStub.restore();
}

assertSpyCalls(successCallback, 0);
assertSpyCalls(errorCallback, 3);
assertSpyCallArg(errorCallback, 0, 0, requestError);
assertSpyCallArg(errorCallback, 1, 0, requestError);
assertSpyCallArg(errorCallback, 2, 0, requestError);
});

it("rely on global config", async () => {
const executeSpy = spy(_internals, "execute");
config.api_key = "test_api_key";
Expand Down