Promise와 async await은 ES5에서 어떻게 바뀔까?

IT공부중·2020년 6월 24일
1

초보자가 공부하며 끄적인 글입니다 ㅎㅎ; 보실 분은 참고만 해주세요.

Promise와 async await 각각 ES6와 ES8에 포함된 문법입니다. modern 브라우저에서는 둘 다 완벽하게 지원한다.(MDN 참고)
하지만 IE 때문에 ES5로 트랜스파일 해줘야하는 경우가 발생한다. IE 점유율 10%미만이라 들었는데 그만 버리면 안 되냐고 ㅠㅠ

babel 설정

그래서 오늘은 Promise와 async await이 ES5로 변환했을 때 어떻게 바뀌는지 알아보기로 했다. 일단 babel을 사용해야 하기 때문에 babel 설정을 해주었다. babel 기본 설정은 다음을 참고했다.

package.json

{
  "name": "mypractice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "babel src -d dist"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {},
  "devDependencies": {
    "@babel/cli": "^7.10.3",
    "@babel/core": "^7.10.3",
    "@babel/preset-env": "^7.10.3"
  }
}

그리고 나서 src 폴더 밑에 test.js라는 파일을 만들어 다음과 같은 코드를 작성하였다.

src/test.js

let promise = new Promise((resolve, reject) => {
  console.log(1);
  return resolve(1);
});

promise.then((value) => console.log(value)).catch((e) => console.error(e));

async function testFunc() {
  let value = await promise;
  console.log(`async ${value}`);
}

testFunc();

간단한 promise와 async await 함수를 작성하였다. 대략 10줄이 조금 넘는 코드다. 이것을 babel을 통해서 ES5문법으로 바꿔 보았다. num run build 실행.

그랬더니 코드가 다음과 같이 변했다.

dist/test.js

"use strict";

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

var promise = new Promise(function (resolve, reject) {
  console.log(1);
  return resolve(1);
});
promise.then(function (value) {
  return console.log(value);
})["catch"](function (e) {
  return console.error(e);
});

function testFunc() {
  return _testFunc.apply(this, arguments);
}

function _testFunc() {
  _testFunc = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    var value;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return promise;

          case 2:
            value = _context.sent;
            console.log("async ".concat(value));

          case 4:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _testFunc.apply(this, arguments);
}

testFunc();

10줄이 조금 넘었던 코드가 40여줄로 변했다. 거의 4배나 상승!! 코드를 보면 asnyc await은 뭐 새로운 함수들도 정의 되고 while 문과 switch문을 써가며 복잡하게 바뀌었다. 그런데 Promise는 거의 바뀐게 없다. 왜냐하면 ES5에는 없는 객체이기 때문이다.
babel은 문법을 변환 해줄 뿐 없던 것을 추가해주지는 않는다. 없던 것을 추가해주기 위해서는 polyfill을 추가해야한다. babel 7부터는 polyfill을 추가하는 방법이 조금 달라진 것 같다. 다음을 참고하여 polyfill을 추가해봤고 실행해보았다.

@babel/polyfill 은 babel 7.4부터 deprecated 됐기 때문에 corejs를 통해 실행해야한다.

npm i -D core-js

를 통해 core-js를 설치한다.

그러면 현재 package.json은 이 상태가 된다.

package.json

{
  "name": "mypractice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "babel src -d dist"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.10.3",
    "@babel/core": "^7.10.3",
    "@babel/preset-env": "^7.10.3",
    "core-js": "^3.6.5"
  }
}

그리고 .babelrc에 다음과 같은 설정을 넣어주면 된다.

.baelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "browsers": ["last 2 versions", "ie >= 11"]
        },
        "useBuiltIns": "usage",
        "corejs": 3,
        "shippedProposals": true
      }
    ]
  ]
}

그러면 이제 다시 npm run build를 해보자.
dist/test.js에 다음과 같이 코드가 바뀐 것을 확인할 수 있을 것이다.

dist/test.js

"use strict";

require("core-js/modules/es.object.to-string");

require("core-js/modules/es.promise");

require("regenerator-runtime/runtime");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

var promise = new Promise(function (resolve, reject) {
  console.log(1);
  return resolve(1);
});
promise.then(function (value) {
  return console.log(value);
}).catch(function (e) {
  return console.error(e);
});

function testFunc() {
  return _testFunc.apply(this, arguments);
}

function _testFunc() {
  _testFunc = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    var value;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return promise;

          case 2:
            value = _context.sent;
            console.log("async ".concat(value));

          case 4:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _testFunc.apply(this, arguments);
}

testFunc();

제일 상단에 corejs에서 es.promise와 es.object.to-string을 가지고 온 것을 볼 수 있다. es5에는 Promise 객체와 같은 template 문자열이 없기 때문에 추가해준 것이다. regenerator-runtime은 core-js에 포함되어 있으므로 따로 설치해줄 필요없다.

webpack 추가하기

이렇게 해주고 나서 index.html에 포함해서 실행시켜 봤더니 require도 없다고 하고 import로 고쳐보아도 해당 파일들이 없다며 잘 안된다.... 그래서 webpack까지 도입해서 실행해보았다.

webpack을 도입하면서 다음과 같이 파일들을 바꾸었다.

package.json

{
  "name": "mypractice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --open --mode development",
    "build": "webpack --mode production",
    "babel": "babel src -d dist"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.10.3",
    "@babel/core": "^7.10.3",
    "@babel/preset-env": "^7.10.3",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "html-webpack-plugin": "^4.3.0",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0",
    "core-js": "^3.6.5"
  }
}

webpack.config.js

const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, options) => {
  const config = {
    entry: { app: ['./src/index.js'] },
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          include: [path.resolve(__dirname, 'src')],
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
          },
        },
      ],
    },
    optimization: { // 코드 스플리팅 해줌.
      splitChunks: {
        cacheGroups: {
          commons: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
          },
        },
      },
    },
  };
  if (options.mode === 'development') {
    config.plugins = [
      new webpack.HotModuleReplacementPlugin(),
      new HtmlWebpackPlugin({
        template: path.join(__dirname, '/public/index.html'),
        showErrors: true,
      }),
    ];
    config.devtool = 'inline-source-map';
    config.devServer = { hot: true, contentBase: './dist' };
  } else {
    config.plugins = [
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        template: path.join(__dirname, '/public/index.html'),
        showErrors: true,
      }),
    ];
  }
  return config;
};

그리고나서 src/public/index.html 파일을 만들어주고 (기본 값들만 들어간 파일)

num run dev

로 실행해주었다.

다음과 같이 잘 되는 것을 볼 수 있었다.

npm run build

를 하게 되면

다음과 같은 파일들이 생기게 된다. app.bundle.js에는 내가 실제로 사용한 es5로 바뀐 문법들이 들어가게 되고 vendors.bundle.js에 corejs로 추가한 여러 값들이 들어간다. 그래서 app.bundle.js에서 Promise라던지 Array의 여러 내부 메소드들을 사용할 수 있게 되는 것이다.

vendors.bundle.js를 보면 regenerationRuntime 같은 함수들이 추가 되어있는 것을 볼 수 있는데 덕분에 IE에서도 작동할 수 있게 된다. target을 IE를 뺀 브라우저들로 잡게 되면 regeneratorRuntime 같은 함수들을 추가 하지 않는 것을 확인할 수 있다!

IE 포함일 때 vendors.bundle.js의 맨 아래 부분

IE에서도 되는 거 확인

Chrome 55 이상일 때 맨 아래 부분

IE에서 오류 발생

크롬은 너무나 잘 된다

결론

corejs3 버전을 사용하면 polyfill을 추가해 IE에서도 Promise나 Array.includes 같은 메소드들을 사용할 수 있다.

Promise는 ES5로 바뀌더라도 그대로이고 polyfill을 통해 Promise 객체를 추가해주어야한다.

async await는 polyfill을 추가해주지 않아도 바뀌며 알아서 따로 async 관련 함수를 만들어주고 while, switch 문 등을 활용해서 만들어준다.

웹팩 및 바벨 설정은 어렵다

profile
3년차 프론트엔드 개발자 문건우입니다.

0개의 댓글