User interaction is naturally asynchronous
JavaScript application runs on a single thread
and JavaScript has had a mechanism for asynchronous execution
.
let button = document.getElementById("btn-hello");
button.onclick = function(event) {
console.log("Clicked");
};
There are paradigms in JavaScript for asynchronous programming: the callback, the promise, and the generator ,and Async-Await (ES2017)
Promises
are relied on callbacksThree primary things that you will be using asynchronous techniques
console.log("Before timeout: " + new Date()); // 1st console.log
function f() {
console.log("After timeout: " + new Date());
}
setTimeout(f, 60*1000); // one minute
console.log("I happen after setTimeout!"); // 2nd console.log
console.log("Me too!"); // 3rd console.log
function countdown() {
let i;
console.log("Countdown:");
for(i=5; i>=0; i--) {
setTimeout(function() {
console.log(i===0 ? "GO!" : i);
}, (5-i)*1000);
}
}
countdown();
for 루프에서 사용된 i 변수가 하나의 스코프에만 존재하며, setTimeout의 콜백 함수들이 모두 같은 i 변수에 대한 참조를 유지한다는 점입니다. 따라서 setTimeout이 실행될 때까지 for 루프가 이미 완료되고, i 변수의 값은 -1이 됩니다.
새로운 스코프를 만들어 각 setTimeout 콜백 함수에 대해 고유한 i 값을 갖도록 하는 것입니다. 이를 위해 함수를 호출하는 IIFE를 사용할 수 있습니다.
function countdown() {
let i;
console.log("Countdown:");
for(i=5; i>=0; i--) {
(function(i) {
setTimeout(function() {
console.log(i===0 ? "GO!" : i);
}, (5-i)*1000);
})(i);
}
}
countdown();
IIFE(Immediately Invoked Function Expression)의 사용은 주로 스코프와 변수의 유효범위와 관련이 있습니다. 몇 가지 장점은 다음과 같습니다:
예를 들어, 앞서 언급한 코드에서의 IIFE는 i
라는 매개변수를 갖고, 각각의 콜백 함수에 대해 새로운 스코프를 만들어 주기 때문에 스코프를 격리하고 변수 충돌을 방지하는 역할을 합니다.
const fs = require('fs');
const fname = 'may_or_may_not_exist.txt';
fs.readFile(fname, function(err, data) {
if(err) return console.error('error reading file ' + fname + ': ' + err.message);
console.log(fname + ' contents: ' + data);
});
const fs = require('fs');
fs.readFile('a.txt', function(err, dataA) {
if(err) console.error(err);
fs.readFile('b.txt', function(err, dataB) {
if(err) console.error(err);
fs.readFile('c.txt', function(err, dataC) {
if(err) console.error(err);
setTimeout(function() {
fs.writeFile('d.txt', dataA+dataB+dataC, function(err) {
if(err) console.error(err);
});
}, 60*1000);
});
});
});
try...catch
blocks only work within the same function.try...catch
블록은 readSketchyFile
에 있지만 fs.readFile
이 콜백으로 호출하는 익명 함수 내부에 오류가 발생합니다.const fs = require('fs');
function readSketchyFile() {
try {
fs.readFile('does_not_exist.txt', function(err, data) {
if(err) throw err;
});
} catch(err) {
console.log('warning: minor issue occurred, program continuing');
}
}
readSketchyFile();
너무 복잡해.. Promise로 해결하자
const fs = require('fs');
function readFileAsync(filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
function writeFileAsync(filename, data) {
return new Promise((resolve, reject) => {
fs.writeFile(filename, data, 'utf8', (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
Promise.all([
readFileAsync('a.txt'),
readFileAsync('b.txt'),
readFileAsync('c.txt')
])
.then(dataArray => {
const [dataA, dataB, dataC] = dataArray;
return new Promise((resolve, reject) => {
setTimeout(() => {
const combinedData = dataA + dataB + dataC;
resolve(combinedData);
}, 60000); // 60-second delay
});
})
.then(combinedData => {
return writeFileAsync('d.txt', combinedData);
})
.then(() => {
console.log('Files a.txt, b.txt, and c.txt were combined and saved as d.txt');
})
.catch(err => {
console.error(err);
});
fulfilled
(success) or rejected
(failure).pending
, fulfilled
, or rejected
.function countdown(seconds) {
return new Promise(function(resolve, reject) { // Anonymous Function (executor)
for(let i=seconds; i>=0; i--) {
setTimeout(function() {
if(i>0) console.log(i + '...');
else resolve(console.log("GO!"));
}, (seconds-i)*1000);
}
});
}
여기서 resolve 대신 reject를 호출해도 되고 error 메시지는 다음과 같이 보낸다.
reject(new Error("message"))
countdown(5).then(
function() {
console.log("countdown completed successfully");
},
function(err) {
console.log("countdown experienced an error: " + err.message);
}
);
const p = countdown(5);
p.then(function() {
console.log("countdown completed successfully");
}).catch(function(err) {
console.log("countdown experienced an error: " + err.message);
});
class MyPromise {
constructor() {
this.promise = new Promise();
}
then(onFulfilled, onRejected) {}
catch(onRejected) {}
}
Generators are functions that use an iterator to control their execution.
Generators are capable of
Generators are like regular functions with two exceptions:
next
method.const gee = function* () { // "Generator { }"
let index = 0;
while(index < 3)
yield index++;
}
const gen=gee()
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // undefined
// ...
gee와 gen의 차이이다.
generatorFunction에서 generator object를 반환해주어야 next 함수를 원활하게 사용할 수 있다.
function declaration 방식을 이용하면 문제가 없지만, 위와 같이 expression 방식을 사용하면 다시 generator object를 반환해주어야 한다는 것을 기억하자.
저기서 gee.next()를 호출하면 next 함수가 없다는 Typeerror가 발생한다.
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i){
yield i;
yield* anotherGenerator(i);
yield i + 10;
}
const gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20
yield 키워드를 통해 값을 반환하고 함수의 실행을 일시 중단할 수 있습니다.
function* rainbow() { // the asterisk marks this as a generator
yield 'red';
yield 'orange';
yield 'yellow';
yield 'green';
yield 'blue';
yield 'indigo';
yield 'violet';
}
const it = rainbow();
it.next(); // { value: "red", done: false }
it.next(); // { value: "orange", done: false }
it.next(); // { value: "yellow", done: false }
it.next(); // { value: "green", done: false }
it.next(); // { value: "blue", done: false }
it.next(); // { value: "indigo", done: false }
it.next(); // { value: "violet", done: false }
it.next(); // { value: undefined, done: true }
// returns an object with two properties: value (which holds the // “color” you’re now on) and
// done (true after the last one)
The first call does not log anything, because the generator was not yielding anything initially.
function* interrogate() {
const name = yield "What is your name?";
const color = yield "What is your favorite color?";
return `${name}'s favorite color is ${color}.`;
}
const it = interrogate();
console.log(it.next());
console.log(it.next('Ethan'));
console.log(it.next('orange'));
function* abc() {
yield 'a';
yield 'b';
return 'c';
}
const it = abc();
console.log(it.next()); // { value: 'a', done: false }
console.log(it.next()); // { value: 'b', done: false }
console.log(it.next()); // { value: 'c', done: true }
generator example에서는 반환값이 undefined가 되어야 done 값이 true가 되었다는 것을 확인했다. 하지만 마지막 값을 반환하면서 done 값을 true로 하고 싶으면 마지막 구문은 yield가 아니라 return으로 작성하면 될 것이다.
const fs = require('fs');
fs.readFile('a.txt', function(err, dataA) {
if(err) console.error(err);
fs.readFile('b.txt', function(err, dataB) {
if(err) console.error(err);
fs.readFile('c.txt', function(err, dataC) {
if(err) console.error(err);
setTimeout(function() {
fs.writeFile('d.txt', dataA+dataB+dataC, function(err) {
if(err) console.error(err);
});
}, 60*1000);
});
});
});
dataA = read contents of 'a.txt'
dataB = read contents of 'b.txt'
dataC = read contents of 'c.txt'
wait 60 seconds
write dataA + dataB + dataC to 'd.txt'
The async function (and await) is added at ECMA2017
The async function declaration defines an asynchronous function, which returns an AsyncFunction object. (async function 선언은 AsyncFunction객체를 반환하는 하나의 비동기 함수를 정의한다)
An asynchronous function is a function which operates asynchronously via the event loop to return its result.
But the syntax and structure using async functions is much more like using standard synchronous functions.
An async function can contain an await expression
An await expression
1) pauses the execution of the async function and
2) waits for the passed Promise's resolution, and then
3) resumes the async function's execution and
4) evaluates as the resolved value.
주의점: the await keyword is only valid inside async functions. If you use it outside of an async function's body, you will get a SyntaxError (i.e., await keyword는 반드시 async 함수 안에서만 쓰여야 함)
async function name([param[, param[, ... param]]]) {
statements
}
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
// expected output: "resolved“
}
asyncCall();
let resolveAfter2Seconds = function() {
console.log("starting slow promise");
return new Promise(resolve => {
setTimeout(function() {
resolve(20);
console.log("slow promise is done");
}, 2000);
});
};
let resolveAfter1Second = function() {
console.log("starting fast promise");
return new Promise(resolve => {
setTimeout(function() {
resolve(10);
console.log("fast promise is done");
}, 1000);
});
};
let sequentialStart = async function() {
const slow = await resolveAfter2Seconds();
console.log(slow);
const fast = await resolveAfter1Second();
console.log(fast);
}
let concurrentStart = async function() {
const slow = resolveAfter2Seconds(); // starts timer immediately
const fast = resolveAfter1Second();
console.log(await slow);
console.log(await fast); // waits for slow to finish, even though fast is already done!
}
sequentialStart()
concurrentStart()