Skip to content
This repository was archived by the owner on Sep 12, 2019. It is now read-only.

Commit 1f37157

Browse files
authored
Fix async and callback combo handling (#166)
Fix async and callback combo handling
2 parents c4477da + f3e7620 commit 1f37157

File tree

3 files changed

+90
-58
lines changed

3 files changed

+90
-58
lines changed

src/utils/serve-functions.js

+90-58
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const path = require("path");
77
const getPort = require("get-port");
88
const chokidar = require("chokidar");
99
const jwtDecode = require("jwt-decode");
10-
// const chalk = require("chalk");
10+
const chalk = require("chalk");
1111
const {
1212
NETLIFYDEVLOG,
1313
// NETLIFYDEVWARN,
@@ -27,60 +27,10 @@ function handleErr(err, response) {
2727
console.log(`${NETLIFYDEVERR} Error during invocation: `, err); // eslint-disable-line no-console
2828
}
2929

30-
function createCallback(response) {
31-
return function(err, lambdaResponse) {
32-
if (err) {
33-
return handleErr(err, response);
34-
}
35-
if (!Number(lambdaResponse.statusCode)) {
36-
console.log(
37-
`${NETLIFYDEVERR} Your function response must have a numerical statusCode. You gave: $`,
38-
lambdaResponse.statusCode
39-
);
40-
return handleErr("Incorrect function response statusCode", response);
41-
}
42-
if (typeof lambdaResponse.body !== "string") {
43-
console.log(
44-
`${NETLIFYDEVERR} Your function response must have a string body. You gave:`,
45-
lambdaResponse.body
46-
);
47-
return handleErr("Incorrect function response body", response);
48-
}
49-
50-
response.statusCode = lambdaResponse.statusCode;
51-
// eslint-disable-line guard-for-in
52-
for (const key in lambdaResponse.headers) {
53-
response.setHeader(key, lambdaResponse.headers[key]);
54-
}
55-
response.write(
56-
lambdaResponse.isBase64Encoded
57-
? Buffer.from(lambdaResponse.body, "base64")
58-
: lambdaResponse.body
59-
);
60-
response.end();
61-
};
62-
}
63-
64-
function promiseCallback(promise, callback) {
65-
if (!promise) return;
66-
if (typeof promise.then !== "function") return;
67-
if (typeof callback !== "function") return;
68-
69-
promise.then(
70-
function(data) {
71-
callback(null, data);
72-
},
73-
function(err) {
74-
callback(err, null);
75-
}
76-
);
77-
}
78-
7930
// function getHandlerPath(functionPath) {
8031
// if (functionPath.match(/\.js$/)) {
8132
// return functionPath;
8233
// }
83-
8434
// return path.join(functionPath, `${path.basename(functionPath)}.js`);
8535
// }
8636

@@ -130,7 +80,12 @@ function createHandler(dir) {
13080

13181
Object.keys(functions).forEach(name => {
13282
const fn = functions[name];
133-
const clearCache = () => {
83+
const clearCache = action => () => {
84+
console.log(
85+
`${NETLIFYDEVLOG} function ${chalk.yellow(
86+
name
87+
)} ${action}, reloading...`
88+
); // eslint-disable-line no-console
13489
const before = module.paths;
13590
module.paths = [fn.moduleDir];
13691
delete require.cache[require.resolve(fn.functionPath)];
@@ -144,9 +99,9 @@ function createHandler(dir) {
14499
ignored: /node_modules/
145100
});
146101
fn.watcher
147-
.on("add", clearCache)
148-
.on("change", clearCache)
149-
.on("unlink", clearCache);
102+
.on("add", clearCache("added"))
103+
.on("change", clearCache("modified"))
104+
.on("unlink", clearCache("deleted"));
150105
});
151106

152107
return function(request, response) {
@@ -167,6 +122,11 @@ function createHandler(dir) {
167122
try {
168123
module.paths = [moduleDir];
169124
handler = require(functionPath);
125+
if (typeof handler.handler !== "function") {
126+
throw new Error(
127+
`function ${functionPath} must export a function named handler`
128+
);
129+
}
170130
module.paths = before;
171131
} catch (error) {
172132
module.paths = before;
@@ -180,7 +140,7 @@ function createHandler(dir) {
180140
if (body instanceof Buffer) {
181141
isBase64Encoded = true;
182142
body = body.toString("base64");
183-
} else if(typeof(body) === "string") {
143+
} else if (typeof body === "string") {
184144
// body is already processed as string
185145
} else {
186146
body = "";
@@ -195,24 +155,96 @@ function createHandler(dir) {
195155
isBase64Encoded: isBase64Encoded
196156
};
197157

158+
let callbackWasCalled = false;
198159
const callback = createCallback(response);
199160
const promise = handler.handler(
200161
lambdaRequest,
201162
{ clientContext: buildClientContext(request.headers) || {} },
202163
callback
203164
);
204-
promiseCallback(promise, callback);
165+
/** guard against using BOTH async and callback */
166+
if (callbackWasCalled && promise && typeof promise.then === "function") {
167+
throw new Error(
168+
"Error: your function seems to be using both a callback and returning a promise (aka async function). This is invalid, pick one. (Hint: async!)"
169+
);
170+
} else {
171+
// it is definitely an async function with no callback called, good.
172+
promiseCallback(promise, callback);
173+
}
174+
175+
/** need to keep createCallback in scope so we can know if cb was called AND handler is async */
176+
function createCallback(response) {
177+
return function(err, lambdaResponse) {
178+
callbackWasCalled = true;
179+
if (err) {
180+
return handleErr(err, response);
181+
}
182+
if (lambdaResponse === undefined) {
183+
return handleErr(
184+
"lambda response was undefined. check your function code again.",
185+
response
186+
);
187+
}
188+
if (!Number(lambdaResponse.statusCode)) {
189+
console.log(
190+
`${NETLIFYDEVERR} Your function response must have a numerical statusCode. You gave: $`,
191+
lambdaResponse.statusCode
192+
);
193+
return handleErr("Incorrect function response statusCode", response);
194+
}
195+
if (typeof lambdaResponse.body !== "string") {
196+
console.log(
197+
`${NETLIFYDEVERR} Your function response must have a string body. You gave:`,
198+
lambdaResponse.body
199+
);
200+
return handleErr("Incorrect function response body", response);
201+
}
202+
203+
response.statusCode = lambdaResponse.statusCode;
204+
// eslint-disable-line guard-for-in
205+
for (const key in lambdaResponse.headers) {
206+
response.setHeader(key, lambdaResponse.headers[key]);
207+
}
208+
response.write(
209+
lambdaResponse.isBase64Encoded
210+
? Buffer.from(lambdaResponse.body, "base64")
211+
: lambdaResponse.body
212+
);
213+
response.end();
214+
};
215+
}
205216
};
206217
}
207218

219+
function promiseCallback(promise, callback) {
220+
if (!promise) return; // means no handler was written
221+
if (typeof promise.then !== "function") return;
222+
if (typeof callback !== "function") return;
223+
224+
promise.then(
225+
function(data) {
226+
console.log("hellooo");
227+
callback(null, data);
228+
},
229+
function(err) {
230+
callback(err, null);
231+
}
232+
);
233+
}
234+
208235
async function serveFunctions(settings) {
209236
const app = express();
210237
const dir = settings.functionsDir;
211238
const port = await getPort({
212239
port: assignLoudly(settings.port, defaultPort)
213240
});
214241

215-
app.use(bodyParser.text({ limit: "6mb", type: ["text/*", "application/json", "multipart/form-data"] }));
242+
app.use(
243+
bodyParser.text({
244+
limit: "6mb",
245+
type: ["text/*", "application/json", "multipart/form-data"]
246+
})
247+
);
216248
app.use(bodyParser.raw({ limit: "6mb", type: "*/*" }));
217249
app.use(
218250
expressLogging(console, {

0 commit comments

Comments
 (0)