前端之异步JS

预备知识

异步的本质
事件队列:
像promise这样的异步操作被放入事件队列中,事件队列在主线程完成处理后运行,这样它们就不会阻止后续JavaScript代码的运行。排队操作将尽快完成,然后将结果返回到JavaScript环境。

在这里插入图片描述

异步概述

什么是同步

通常, 程序只有一个主线程, 同一时间只能做一件事,其他的事情都阻塞了,直到前面的操作完成, 才开始后面的操作.

什么是异步

异步是指 多个事件的发生不会互相阻塞干扰. 同一时间可以做多个事情.
使用方式: 从网络获取文件,访问数据库,从网络摄像头获得视频流,或者向VR头罩广播图像等.

线程

程序用线程完成任务, 而每个线程只能执行一个任务.
由于所有任务都是顺序执行的, 所以只有前面的任务结束, 后面的任务才可以执行.

在这里插入图片描述
现代计算机是多核的, 可以支持多线程:
在这里插入图片描述

JS是单线程的

只能在一个线程( 主线程 : main thread )上运行多个程序.
js 的异步操作依靠下面两种方法 :

  • callback
  • promise

异步简介

异步的两种使用方式

callbacks (老派异步)

callback 回调

callbacks 异步情况

A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
异步callbacks 其实就是函数,只不过是作为参数传递给那些在后台执行的其他函数. 当那些后台运行的代码结束,就调用callbacks函数,通知你工作已经完成.
举例: 异步callback 就是addEventListener()第二个参数以及setTimeout函数的第一个参数

回调函数用途广泛 — 他们不仅仅可以用来控制函数的执行顺序和函数之间的数据传递,还可以根据环境的不同,将数据传递给不同的函数,所以对下载好的资源,你可以采用不同的操作来处理,譬如 processJSON(), displayText(), 等等。

callbacks 同步情况
const gods = ['Apollo', 'Artemis', 'Ares', 'Zeus'];

gods.forEach(function (eachName, index){
  console.log(index + '. ' + eachName);
});

forEach() 的参数是一个回调函数,回调函数本身带有两个参数,数组元素和索引值。它无需等待任何事情,立即运行。

promise (新派异步)

Promises 是新派的异步代码, fetch() API就是一个很好的例子, 相当于一个更高效的 XMLHttpRequest .

fetch('products.json').then(function(response) {
  return response.json();
}).then(function(json) {
  products = json;
  initialize();
}).catch(function(err) {
  console.log('Fetch problem: ' + err.message);
});
  1. 这里fetch() 只需要一个参数— 资源的网络 URL — 返回一个 promise.
    promise 是表示异步操作完成或失败的对象。可以说,它代表了一种中间状态。 本质上,这是浏览器说“我保证尽快给您答复”的方式,因此得名“promise”。

  2. then()中都包含一个回调函数, 如果前一个操作成功,该函数将运行,并且每个回调都接收前一个成功操作的结果作为输入,因此您可以继续对它执行其他操作。每个then()返回一个promise, 那么多个then()可以执行多个异步操作。

  3. 如果任意一个then()发生错误, 就会运行catch(), 用来报告发生的错误类型。try…catch不能与promise一起使用.

Promises 对比 callbacks

promises与callbacks相似。它们本质上是一个返回的对象,并将回调函数附加到该对象上,而不必将回调作为参数传递给另一个函数。

然而,Promise是专门为异步操作而设计的,与旧式回调相比具有许多优点:

  1. **您可以使用多个then()操作将多个异步操作链接在一起,并将其中一个操作的结果作为输入传递给下一个操作。**这种链接方式对回调来说要难得多,会使回调以混乱的“末日金字塔”告终。 (也称为回调地狱)。
  2. Promise总是严格按照它们放置在事件队列中的顺序调用。
  3. 错误处理: 所有的错误都由块末尾的一个.catch()块处理

介绍异步JS

JS异步执行的函数

在一段时间后异步执行代码,
或重复异步执行代码,直到让它停下.

setTimeout()

在指定的时间后执行一段代码. 递归使用时, 无论代码运行多久, 每次代码运行间隔都是相同的.
在这里插入图片描述
超时执行
设置限定时间

语法:

let 标识符 = setTimeout("要执行的代码", 等待的毫秒数);
setTimeout("alert('对不起, 要你久候')", 3000 );

setTimeout("JavaScript 函数", 等待的毫秒数);
setTimeout("函数名()", 等待的毫秒数);


//**清除 setTimeout()** 用
clearTimeout(标识符);

调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。但是,如果有其它消息,setTimeout 消息必须等待其它消息处理完。因此第二个参数仅仅表示最少的时间 而非确切的时间

如果等待的毫秒数为零, 则表示尽快执行., 表示主线程运行后立刻执行.

setInterval()

设置时间间隔, 重复执行.
假设代码需要40毫秒才能运行 - 那么间隔最终只有60毫秒。

语法:

var intervalID = setInterval(func, [delay, arg1, arg2, ...]);
  • func 是函数, 经过delay时间后,执行这个函数.
  • arg 是按顺序传入func函数的参数.
  • intervalID 是setInterval() 计时器的唯一标识符, 通过 clearInterval()来删除setInterval()计时器.
function displayTime() {
   let date = new Date();
   let time = date.toLocaleTimeString();
   document.getElementById('demo').textContent = time;
}

const createClock = setInterval(displayTime, 1000);

//清除interval
clearInterval(createClock);

实例 – 秒级计时器

代码的运行时间可能长于分配的时间间隔, 所以最好使用递归的setTimeut()

当你的代码有可能比你分配的时间间隔,花费更长时间运行时,最好使用递归的 setTimeout() - 这将使执行之间的时间间隔保持不变,无论代码执行多长时间,你不会得到错误

requestAnimationFrame() √

相当于setInterval(), 但进行了改进, requestAnimationFrame()是专门用来循环的函数, 目的 是在浏览器中高效运行动画. 通常每秒60次.

具体情况 它在浏览器重新加载显示内容之前执行指定的代码块,从而允许动画以适当的帧速率运行.

<body>
    <div></div>

    <script>
        const spinner = document.querySelector('div');
        let rotateCount = 0;
        let rAF; // requestAnimationFrame()标识符
        let startTime = null; // 开始时间戳
        let spinning = false;

        function draw(timestamp) {
            if (!startTime) {
                startTime = timestamp;
            }
            rotateCount = (timestamp - startTime) / 3;

            if (rotateCount > 359) {
                rotateCount %= 360;
            }
            spinner.style.transform = `rotate(${rotateCount}deg)`;
            rAF = requestAnimationFrame(draw);
        }
        document.body.addEventListener('click', () => {
            if (spinning) {
                cancelAnimationFrame(rAF);//取消动画
                spinning = false;
            } else {
                draw();
                spinning = true;
            }
        });
    </script>
</body>

用于 更新动画 (例如,移动精灵,更新乐谱,刷新数据等).

动画的平滑度直接取决于动画的帧速率;
每秒帧数(fps)的数字越高,动画就越平滑;

由于大多数屏幕的刷新率为60Hz,因此在使用web浏览器时,可以达到的最快帧速率是每秒60帧(FPS)。然而,更多的帧意味着更多的处理,这通常会导致卡顿和跳跃-也称为丢帧或跳帧。

requestAnimationFrame() 会尽其所能利用现有资源提升帧速率。总是努力达到60帧/秒的值. 所以我们还要注意每次通过动画循环时要运行的代码量。

timestamp
时间戳系统
用来产生和管理时间戳,对签名对象进行数字签名产生时间戳,以证明原始文件在签名时间之前已经存在。

实例 – 拼手速

Promise的使用

Promise 是 JavaScript 语言的一个较新的功能,允许推迟进一步的操作,直到上一个then()操作完成或失败.

  • Promise 是一个对象,代表操作的中间状态;
  • Promise (承诺) 保证在未来返回结果(成功, 正确处理结果; 错误, 处理错误);
    在这里插入图片描述

常见应用:
Promise 常见的交互之一就是 Web API 返回的 promise 对象。getUserMedia() 来访问用户的摄像头和麦克风。

promise 的使用

chooseToppings()
.then(function(toppings) {
  return placeOrder(toppings);
})
.then(function(order) {
  return collectOrder(order);
})
.then(function(pizza) {
  eatPizza(pizza);
})
.catch(failureCallback);

promise 的简化(箭头函数)

chooseToppings()
.then(toppings =>
  placeOrder(toppings)
)
.then(order =>
  collectOrder(order)
)
.then(pizza =>
  eatPizza(pizza)
)
.catch(failureCallback);

// 或者
chooseToppings()
.then(toppings => placeOrder(toppings))
.then(order => collectOrder(order))
.then(pizza => eatPizza(pizza))
.catch(failureCallback);

箭头函数 () => x 的原型是 ()=> {return x;}

promise 的注意事项

  • promise与事件监听器类似
  • 一个promise只能成功或失败一次。一旦失败就执行catch()函数

promise实例

fetch和then会按顺序执行,fetch和then成功,会将返回值交给下一个,若以上过程有错,则直接执行catch块。

URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL
语法

objectURL = URL.createObjectURL(object);

一下两种写法等价:

        // 调用 fetch() 方法,将图像的URL作为参数从网络中提取
        // fetch() 返回的promise对象存储在一个名为promise的变量
        let promise = fetch('https://img1.baidu.com/it/u=2769047888,512921539&fm=253&fmt=auto&app=138&f=JPEG?w=657&h=500');
        let promise2 = promise.then(reponse => {
            if (!reponse.ok) { //Fetch promises 不会产生 404 或 500错误, 这里就是为了产生404错误
                // console.log(reponse.ok);
                throw new Error(`HTTP error! status: ${response.this.state}`);
            } else {
                return reponse.blob();
            }
        })
        let promise3 = promise2.then(myBlob => {
            let objectURL = URL.createObjectURL(myBlob);
            let image = document.createElement('img');
            image.src = objectURL;
            document.body.appendChild(image);
        });

        // promise响应失败情况
        let errorCase = promise3.catch(e => {
            console.log('There has been a problem with your fetch operation: ' + e.message);
        });

等价于

		fetch('https://img1.baidu.com/it/u=2769047888,512921539&fm=253&fmt=auto&app=138&f=JPEG?w=657&h=500')
            .then(response => {
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.this.state}`);
                } else {
                    return response.blob();
                }
            })
            .then(myBlob => {
                let objectURL = URL.createObjectURL(myBlob);
                let image = document.createElement('img');
                image.src = objectURL;
                document.body.appendChild(image);
            })
            .catch(e => {
                console.log('There has been a problem with your fetch operation: ' + e.message);
            });

Promise.all()

Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组

  • 若参数是数组,返回值也是数组,内部元素都是promise(fetch的返回值)。
  • 响应多个promise,在执行对应代码。
  • 任何一个 promise 拒绝,整个块将拒绝。

语法:

// a,b,c 都是promise类型
Promise.all([a, b, c])
.then(values => {
  ...
})
.then()// 无错情况下, 顺序执行
.catch()// 有错执行
.finally(); // promise 最后一定执行

模板实例: √

  • 自定义promise例子
// 根据url获取资源, 根据type进行解读(图像blob 和文本text)
function fetchAndDecode(url, type) {
  return fetch(url).then(response => {
    if(!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    } else {
      if(type === 'blob') {
        return response.blob();
      } else if(type === 'text') {
        return response.text();
      }
    }
  })
  .catch(e => {
    console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
  })
  .finally(() => {
    console.log(`fetch attempt for "${url}" finished.`);
  });
}

入门实例1:

        let timeoutPromise = new Promise((resolve, reject) => {
            setTimeout(function() {
                resolve('Success!');
            }, 2000);
        })

        timeoutPromise
            .then(message => {
                alert(message);
            })

入门实例1 改进:

 function timeoutPromise(message, interval) {
     return new Promise((resolve, reject) => {
         if (message === '' || typeof message !== 'string') {
             reject('Message is empty or not a string');
         } else if (interval < 0 || typeof interval !== 'number') {
             reject('Interval is negative or not a number');
         } else {
             setTimeout(function() {
                 resolve(message);
             }, interval);
         }
     });
 };

 timeoutPromise('Hello there!', 1000)
     .then(message => {
         alert(message);
     })
     .catch(e => {
         console.log('Error: ' + e);
     });

以上例子是故意简单, 下方一个更真实的例子

function promisifyRequest(request) {

  return new Promise(function(resolve, reject) {
    request.onsuccess = function() {
      resolve(request.result);
    };

    request.onerror = function() {
      reject(request.error);
    };
  });
  
}

request 等价于 fetch(URL)

  1. 当request的success event触发时,onsuccess处理程序将使用请求的result实现(fullfill)promise。
  2. 当request的error event触发时,onerror处理程序拒绝带有请求error的promise

异步编程的简单实现

Promises 有点复杂, 但是现代的浏览器都配备了 async 函数和 await 操作符

async

async 关键字加到函数声明中,表示返回值是 promise 类型。

async 加到函数之前,使它成为异步函数,该函数会返回一个 promise。使用返回值 promise 时,用 .then(value => {})
语法:

async function name([param[, param[, ...param]]]) {
   statements
}

在这里插入图片描述

let hello = async () => { return "Hello" };
hello().then((value) => console.log(value))

await

await 只在异步函数里面才起作用。是配合 async 一起使用的
位置: 任何异步的,基于 promise 的函数之前。包括Web API函数。
作用: 暂停在该行代码上,直到 promise 完成;
暂停时,其他正在等待执行的代码就有可以执行了。

        // 图片
        async function myFetch(url) {
            let response = await fetch(url);
            return response.blob();
        }
        myFetch('https://img1.baidu.com/it/u=3702625202,3169032464&fm=253&fmt=auto&app=138&f=JPEG?w=667&h=500')
            .then(myBlob => {
                console.log(myBlob);
                let objectURL = URL.createObjectURL(myBlob);
                let image = document.createElement('img');
                image.src = objectURL;
                document.body.appendChild(image);
            })
            .catch(e => {
                console.log('There has been a problem with your fetch operation: ' + e.message);
            });

Promise.all()

        async function fetchAndDecode(url, type) {
            let response = await fetch(url);

            let content;

            if (type === 'blob') content = await response.blob();
            else if (type === 'text') content = await response.text();

            return content;
        }

        async function displayContent() {
            let a = fetchAndDecode('https://img1.baidu.com/it/u=3702625202,3169032464&fm=253&fmt=auto&app=138&f=JPEG?w=667&h=500', 'blob');
            let b = fetchAndDecode('https://img1.baidu.com/it/u=2769047888,512921539&fm=253&fmt=auto&app=138&f=JPEG?w=657&h=500', 'blob');
            let c = fetchAndDecode('https://img2.baidu.com/it/u=703207382,3730089159&fm=253&fmt=auto&app=138&f=JPEG?w=260&h=195', 'blob');

            let values = await Promise.all([a, b, c]);

            let ObjectURL1 = URL.createObjectURL(values[0]);
            let ObjectURL2 = URL.createObjectURL(values[1]);
            let ObjectURL3 = URL.createObjectURL(values[2]);

            let image1 = document.createElement('img');
            let image2 = document.createElement('img');
            let image3 = document.createElement('img');

            image1.src = ObjectURL1;
            image2.src = ObjectURL2;
            image3.src = ObjectURL3;

            document.body.appendChild(image1);
            document.body.appendChild(image2);
            document.body.appendChild(image3);
        }

        displayContent()
            .catch(e => console.log)

注: Async/await 让你的代码更同步,但await 关键字会阻塞其后的await代码,直到promise完成。如果await比较多,会阻塞异步程序自身的运行。
将Promise对象存储在变量中,同时开始它们,最后等到完全执行完毕。这样可以缓解上述问题。

将可以同时进行的await操作放入变量中同时执行

function timeoutPromise(interval) {
  return new Promise((resolve, reject) => {
    setTimeout(function(){
      resolve("done");
    }, interval);
  });
};
//原型 9s多 --------------------------------------------------
async function timeTest() {
  await timeoutPromise(3000);
  await timeoutPromise(3000);
  await timeoutPromise(3000);
}
let startTime = Date.now();
timeTest().then(() => {
  let finishTime = Date.now();
  let timeTaken = finishTime - startTime;
  alert("Time taken in milliseconds: " + timeTaken);
})

//改 3s多 -------------------------------------------------- 
async function timeTest() {
  const timeoutPromise1 = timeoutPromise(3000);
  const timeoutPromise2 = timeoutPromise(3000);
  const timeoutPromise3 = timeoutPromise(3000);

  await timeoutPromise1;
  await timeoutPromise2;
  await timeoutPromise3;
}

let startTime = Date.now();
timeTest().then(() => {
  let finishTime = Date.now();
  let timeTaken = finishTime - startTime;
  alert("Time taken in milliseconds: " + timeTaken);
})

Async/await 的类方法

class Person {
  constructor(first, last, age, gender, interests) {
    this.name = {
      first,
      last
    };
    this.age = age;
    this.gender = gender;
    this.interests = interests;
  }

  async greeting() {
    return await Promise.resolve(`Hi! I'm ${this.name.first}`);
  };

  farewell() {
    console.log(`${this.name.first} has left the building. Bye for now!`);
  };
}

let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);

han.greeting().then(console.log);

正确选择实现异步的方法 √

异步回调

特征:将回调函数作为参数传递给另一个函数,
作用:方便对异步操作的返回结果进行处理。

实例:XMLHttpRequest API加载资源
XMLHttpRequest从 URL 检索数据,不会刷新整个页面。只更新页面的一部分,不会中断用户正在做的事情。

  function loadAsset(url, type, callback) {
            let xhr = new XMLHttpRequest();
            xhr.open('GET', url);
            xhr.responseType = type;

            xhr.onload = function() {
                console.log(xhr.response);
                callback(xhr.response);
            };
            xhr.send();
        }

        function displayImage(blob) {
            let objectURL = URL.createObjectURL(blob);

            let image = document.createElement('img');
            image.src = objectURL;

            document.body.appendChild(image);
        }

        loadAsset('https://img2.baidu.com/it/u=1814268193,3619863984&fm=253&fmt=auto&app=138&f=JPEG?w=632&h=500', 'blob', displayImage);

setTimeout()

特征:经过指定时间(单位:ms)后运行函数。

作用:无论代码运行时间是多少,间隔时间一定会是指定的时长。(可以递归调用)

        let sayHello = setTimeout(() => {
            alert('Hello!')
        }, 2000);

setTimeout()的递归用法及退出递归

        let i = 0;
        let a = setTimeout(function sayNumber() {
            console.log(i++);
            a = setTimeout(sayNumber, 1000);
        }, 1000);
        document.addEventListener('click', () => {
            clearTimeout(a);
        })

setInterval()

特征:重复执行一个函数,执行时间包含在时间间隔中。

作用:效率比requestAnimationFrame()低,但可以指定时间间隔
实例:

    <div id="nowTime"></div>
    <div id="nowDate"></div>
        setInterval(() => {
            let date = new Date();
            let nowTime = date.toLocaleTimeString();
            let nowDate = date.toDateString();
            document.querySelector('#nowTime').textContent = nowTime;
            document.querySelector('#nowDate').textContent = nowDate;
        }, 1000);

执行时间包含在时间间隔中

在这里插入图片描述

requestAnimationFrame()

特征: requestAnimationFrame()会自动循环以达到最佳帧速率(60帧)重复且高效地运行函数。
作用:不可以指定帧率,只有在指定帧率的情况下才使用setInterval() / setTimeout()。

实例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>异步实例</title>
    <style>
        div {
            display: inline-block;
        }
    </style>
</head>

<body>
    <div></div>

    <script>
        const spinner = document.querySelector('div');
        let rotateCount = 0; // 旋转角度
        let startTime = null;
        let rAF;

        function draw(timestamp) {
            if (!startTime) {
                startTime = timestamp;
            }

            let rotateCount = (timestamp - startTime) / 3;

            spinner.style.transform = 'rotate(' + rotateCount + 'deg)';

            if (rotateCount > 359) {
                rotateCount = 0;
            }

            rAF = requestAnimationFrame(draw);
        }

        draw();
    </script>
</body>
</html>

promises

特征:进行异步操作,再用异步结果进行下一步操作。
作用:Promises链可能很复杂。如果你嵌套了许多promises,可能会变成回调地狱。
实例:从服务器获取图片,并添加到网页中

        fetch('https://img1.baidu.com/it/u=4127991555,3421789262&fm=253&fmt=auto&app=138&f=JPEG?w=680&h=454')
            .then(response => response.blob())
            .then(myBlob => {
                let objectURL = URL.createObjectURL(myBlob);
                let image = document.createElement('img');
                image.src = objectURL;
                document.body.appendChild(image);
            })
            .catch(e => {
                console.log('There has been a problem with your fetch operation: ' + e.message);
            });

Promise.all()

特征:等待多个promises完成,然后根据所有的结果进行进一步操作。
作用:从服务器获取多个资源,并使用Promise.all()等待所有资源可用。
实例:从服务器获取多个图片,并添加到网页中

        function fetchAndDecode(url, type) {
            return fetch(url).then(response => {
                    if (type === 'blob') {
                        return response.blob();
                    } else if (type === 'text') {
                        return response.text();
                    }
                })
                .catch(e => {
                    console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
                });
        }

        let coffee = fetchAndDecode('https://img1.baidu.com/it/u=4127991555,3421789262&fm=253&fmt=auto&app=138&f=JPEG?w=680&h=454', 'blob');
        let tea = fetchAndDecode('https://img0.baidu.com/it/u=1721391133,702358773&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=625', 'blob');

        Promise.all([coffee, tea]).then(values => {
            console.log(values);
            let objectURL1 = URL.createObjectURL(values[0]);
            let objectURL2 = URL.createObjectURL(values[1]);

            let image1 = document.createElement('img');
            let image2 = document.createElement('img');
            image1.src = objectURL1;
            image2.src = objectURL2;
            document.body.appendChild(image1);
            document.body.appendChild(image2);
        });

async/await

特征:像编写同步回调代码的语法来运行异步操作。
作用:async 写在 function 前,await 写在async函数内,等待一个Promise 对象。如果返回值不是promise对象,await 会把该值转换为已正常处理的Promise
实例:

        // 图片
        async function myFetch(url) {
            let response = await fetch(url);
            return response.blob();
        }
        myFetch('https://img1.baidu.com/it/u=3702625202,3169032464&fm=253&fmt=auto&app=138&f=JPEG?w=667&h=500')
            .then(myBlob => {
                console.log(myBlob);
                let objectURL = URL.createObjectURL(myBlob);
                let image = document.createElement('img');
                image.src = objectURL;
                document.body.appendChild(image);
            })
            .catch(e => {
                console.log('There has been a problem with your fetch operation: ' + e.message);
            });
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值