likes
comments
collection

使用 Promise 来改写 JavaScript 的加载逻辑

作者站长头像
站长
· 阅读数 1

传统的基于 callback 的实现方式:

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error(`Script load error for ${src}`));

  document.head.append(script);
}

如果使用 promise,则避免了回调函数的使用。

新函数 loadScript 不需要回调。 相反,它将创建并返回一个 Promise 对象,该对象在加载完成时解析。 外部代码可以使用 .then 向其添加处理程序(订阅函数):

function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error(`Script load error for ${src}`));

    document.head.append(script);
  });
}

如何使用这个 loadScript

let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");

promise.then(
  script => alert(`${script.src} is loaded!`),
  error => alert(`Error: ${error.message}`)
);

promise.then(script => alert('Another handler...'));

使用 Promise 编写代码,允许我们按照自然顺序做事。 首先,我们运行 loadScript(script),然后我们使用类似自然语言的语法 then,编写如何处理结果。

反观 callback 解决方案,调用 loadScript(script, callback) 时,我们必须有一个回调函数可供使用。 换句话说,我们必须在调用 loadScript 之前知道如何处理结果。

我们可以根据需要多次调用 .then 。 每次,我们都会在“订阅列表”中添加一个新的“粉丝”,一个新的订阅功能。而 Callback 的解决方案,只支持单个 Callback.

我们如果多次在 executor 里调用 resolve,会发生什么情形?

比如考虑上述代码:

let promise = new Promise(function(resolve, reject) {
  resolve(1);

  setTimeout(() => resolve(2), 1000);
});

promise.then(alert);

答案是 1.

第二次调用resolve被忽略,因为只考虑第一次reject/resolve调用。 进一步的调用将被忽略。

promise 有很多妙用,比如用来实现 delay 函数。

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

delay(3000).then(() => alert('runs after 3 seconds'));

delay 的 executor 里,三秒之后再调用 resolve,因而实现了 delay 效果。

请注意,在这个任务中,resolve 的写法,含义是 resolve 被调用,没有参数。 我们不会从延迟中返回任何值,只是确保延迟。

下面是用 promise 实现的一个特效:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <style>
    .message-ball {
      font-size: 20px;
      line-height: 200px;
      text-align: center;
    }
    .circle {
      transition-property: width, height;
      transition-duration: 2s;
      position: fixed;
      transform: translateX(-50%) translateY(-50%);
      background-color: red;
      border-radius: 50%;
    }
  </style>
</head>

<body>

  <button onclick="go()">Click me</button>

  <script>

  function go() {
    showCircle(150, 150, 100).then(div => {
      div.classList.add('message-ball');
      div.append("Hello, world!");
    });
  }

  function showCircle(cx, cy, radius) {
    let div = document.createElement('div');
    div.style.width = 0;
    div.style.height = 0;
    div.style.left = cx + 'px';
    div.style.top = cy + 'px';
    div.className = 'circle';
    document.body.append(div);

    return new Promise(resolve => {
      setTimeout(() => {
        div.style.width = radius * 2 + 'px';
        div.style.height = radius * 2 + 'px';

        div.addEventListener('transitionend', function handler() {
          div.removeEventListener('transitionend', handler);
          resolve(div);
        });
      }, 0);
    })
  }
  </script>


</body>
</html>

点击 Click me 按钮后,会出现一个圆形。

使用 Promise 来改写 JavaScript 的加载逻辑