JS中的异步编程

前言

  fetch 是用来替代传统的XMLHttpRequest的。 fetch 的优点很多,包括链式调用的语法、返回promise等。

什么是Promise?

  当在开发中需要处理异步操作时,Promise是一个常用的工具。Promise是一个表示异步操作最终完成或失败的对象。

  创建一个Promise对象时,可以传入一个执行器函数,该函数接受两个参数:resolve 和 reject 。通过调用resolve函数,表示异步操作成功并传递结果,而调用reject函数表示异步操作失败并传递错误信息。

  以下是一个使用Promise的简单示例:

// 创建一个简单的异步操作
const fetchData = () => {
    return new Promise((resolve, reject) => {
        // 模拟异步操作,比如从服务器获取数据
        setTimeout(() => {
            const data = {id: 1, name: 'John'};
            if (data) {
                resolve(data); // 异步操作成功
            } else {
                reject('Failed to fetch data.'); // 异步操作失败
            }
        }, 2000);
    });
};

// 使用Promise处理异步操作
fetchData()
    .then(data => {
        console.log('异步操作成功:', data);
        // 在这里可以对获取的数据进行处理
    })
    .catch(error => {
        console.log('异步操作失败:', error);
        // 在这里可以处理异步操作失败的情况
    });

  在上面的示例中,fetchData 函数返回一个 Promise 对象,通过调用 resolve 和 reject 函数来表示异步操作的结果。然后可以使用.then()来处理异步操作成功的情况,并使用.catch()来处理异步操作失败的情况。

  当异步操作成功时,.then()中的回调函数会被调用,并将异步操作的结果传递给它。当异步操作失败时,.catch()中的回调函数会被调用,并将错误信息传递给它。

  这只是Promise的基本用法,还可以使用更多的方法和技巧来处理Promise,例如使用Promise.all()来处理多个异步操作,使用async/await来编写更简洁的异步代码等。

测试示例

// 使用示例1
new Promise(function (resolve, reject) {
    var a = 0;
    var b = 1;
    if (b == 0) {
        // 触发catch块中的逻辑
        reject("除零错误");
    } else {
        // 触发then块中的逻辑
        resolve(a / b);
    }
}).then(function (value) {
    // 由resolve触发
    console.log("a / b = " + value);
}).catch(function (err) {
    // 由reject触发
    console.log(err);
}).finally(function () {
    // 无论如何要执行的语句
    console.log("最后finally块中的代码");
});

// 使用示例2
new Promise(function (resolve, reject) {
    console.log(1);
    resolve(2);
}).then(function (value) {
    console.log(value);
    return 3;
}).then(function (value) {
    console.log(value);
    throw "抛出错误";
}).catch(function (err) {
    console.log(err);
});

为什么用fetch?

fetch 官网:https://github.com/github/fetch

fetch 是 JavaScript 提供的一种现代化的网络请求 API,用于进行网络请求并获取资源。它是基于 Promise 的,使用起来相对简单和灵活。

传统的 xhr 请求写起来非常的混乱,如下所示:

例1:

var xhr = new XMLHttpRequest();
xhr.open('GET', "https://api.github.com");
xhr.responseType = 'json';

// 设置成功回调
xhr.onload = function() {
    console.log(xhr.response);
};

// 设置错误回调
xhr.onerror = function() {
    console.log("请求发生错误!");
};

// 发送请求
xhr.send();

例2:

// 需要目标站点后台服务器给响应头设置允许跨域的标识头
function sendAjax() {
    var req = new XMLHttpRequest();
    req.open('GET', "https://api.github.com");
    req.send();
    // 设置成功回调
    req.onreadystatechange = function () {
        if (req.readyState === 4) {
            try {
                alert(req.responseText);
                b = req.response;
            } catch (e) {
                alert('你访问的页面出错了');
            }
        }
    };
    // 设置请求超时回调
    req.ontimeout = function () {
        console.log("请求超时")
    }
}
sendAjax()

但使用 fetch 之后,如下所示:

fetch("https://api.github.com")
    .then(response => response.blob())  // 响应的json文本数据使用 response.json()
    .then(data => console.log(data))
    .catch(e => console.log(e));

  在上面的示例中,使用 fetch 函数来发起一个 GET 请求,并传递目标 URL 作为参数。fetch 返回一个 Promise 对象,我们可以使用 .then() 来处理请求成功的情况,通过 .catch() 来处理请求失败的情况。

在第一个 .then() 中,我们使用 response.json() 方法将响应数据解析为 JSON 格式。这个方法也返回一个 Promise 对象,我们可以再次使用 .then() 来处理解析后的数据。

  如果需要发送 POST 请求或者设置其他请求参数,fetch 还提供了相应的选项。下面是一个带有 POST 请求和请求头设置的示例:

fetch('https://api.example.com/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({username: 'john', password: 'secret'})
})
    .then(response => response.json())
    .then(data => {
        console.log('获取到的数据:', data);
        // 在这里可以对获取到的数据进行处理
    })
    .catch(error => {
        console.log('请求失败:', error);
        // 在这里可以处理请求失败的情况
    });

  在上面的示例中,通过传递第二个参数来设置请求选项。method 设置为 'POST' 表示发送一个 POST 请求,headers 设置了请求头的 'Content-Type''application/json'body 是一个包含要发送的数据的对象,并使用 JSON.stringify 将其转换为 JSON 字符串。

  使用 fetch 进行网络请求的优势之一是可以与其他 JavaScript 特性(如 async/awaitPromise.all)结合使用,从而编写更简洁和灵活的代码。

async、await

asyncawait 是 JavaScript 中用于处理异步操作的关键字。它们可以用更简洁、更易读的方式编写异步代码。

async 关键字用于定义一个异步函数,异步函数内部可以包含 await 关键字。异步函数在被调用时会返回一个 Promise 对象,该对象会在异步操作完成时解析为返回值。

下面是一个简单的示例,展示了如何使用 asyncawait

// 异步函数
async function fetchData() {
    // 模拟异步操作,比如从服务器获取数据
    const response = await fetch('https://api.github.com/users/mzabriskie');
    const data = await response.json();
    return data;
}

// 使用异步函数
async function processData() {
    try {
        const result = await fetchData();
        console.log('异步操作成功:', result);
        // 在这里可以对获取的数据进行处理
    } catch (error) {
        console.log('异步操作失败:', error);
        // 在这里可以处理异步操作失败的情况
    }
}

// 调用异步函数
processData();

  在上面的示例中,fetchData 是一个异步函数,内部使用了 await 关键字来等待异步操作的结果。fetch 函数返回一个 Promise 对象,使用 await 等待该 Promise 对象解析为结果。类似地,response.json() 也返回一个 Promise 对象,也需要使用 await 等待结果。

  在 processData 函数中,我们使用 try...catch 来捕获可能发生的错误。如果异步操作成功,await 表达式会返回异步操作的结果,并将其赋值给 result 变量。如果异步操作失败,会抛出一个错误,被 catch 块捕获并进行处理。

  总结一下,async 关键字用于定义异步函数,await 关键字用于等待异步操作的结果。它们的使用可以让异步代码更加清晰、易读,并且可以使用类似同步代码的方式进行编写。

  • 异步执行验证:最后打印的时间相差在1秒内即为异步执行
async function test() {
    let b = 0;
    for (let i = 0; i < 100000000; i++) {
        b++;
    }
    console.log(b)
    console.log(new Date())
}

for (var i = 0; i < 10; i++) {
    test()
}
  • 异步请求示例
// async function
async function fetchAsync() {
    // await response of fetch call
    let response = await fetch('https://api.github.com');
    // only proceed once promise is resolved
    let data = await response.json();
    // only proceed once second promise is resolved
    return data;
}

// trigger async function
// log response or catch error of fetch promise
fetchAsync()
    .then(data => console.log(data))
    .catch(e => console.log(e.message))

基本使用方法

fetch 必须接受一个资源路径作为参数,并且返回了一个promise,所以我们可以直接使用链式调用的方式。

ajax请求

fetch("/getAllUserInfo")
    .then(res => res.json())
    .then(data => {
        if (data.code === 200) {
            console.log('获取所有用户信息', data.data);
            // 其它业务逻辑
        } else {
            console.log(data.message);
        }
    })

自定义请求

fetch 提供了对 Request 和 Response (以及其他与网络请求有关的)对象的通用定义。所以在一个Fetch请求中,完全可以只使用Request 和 Response两个对象,通过Request 设置参数,通过Response 对返回值进行处理。

var myHeaders = new Headers();
myHeaders.append('Content-Type', 'image/jpeg');
var option = {
    method: 'GET',
    headers: myHeaders,
    mode: 'cors',
    cache: 'default'
};
var myRequest = new Request('https://api.github.com/users/mzabriskie', option);
fetch(myRequest).then(response => {
    console.log(response)
    return response.json()
}).then(data => console.log(data));

HTML请求

fetch('/users.html')
  .then(function(response) {
    return response.text()
  }).then(function(body) {
    document.body.innerHTML = body
  })

JSON请求

fetch('/users.json')
  .then(function(response) {
    return response.json()
  }).then(function(json) {
    console.log('parsed json', json)
  }).catch(function(ex) {
    console.log('parsing failed', ex)
  })

Response metadata

fetch('/users.json').then(function(response) {
  console.log(response.headers.get('Content-Type'))
  console.log(response.headers.get('Date'))
  console.log(response.status)
  console.log(response.statusText)
})

Post form

var form = document.querySelector('form')
fetch('/users', {
  method: 'POST',
  body: new FormData(form)
})

Post JSON

fetch('/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Hubot',
    login: 'hubot',
  })
})

File upload

var input = document.querySelector('input[type="file"]')
var data = new FormData()
data.append('file', input.files[0])
data.append('user', 'hubot')
fetch('/avatars', {
  method: 'POST',
  body: data
})

注意点

1、 fetch api 提供的spi囊括但是不限于xhr的所有功能。

2、 fetch api 可以跨域,参考: https://fetch.spec.whatwg.org/#http-cors-protocol

要求响应对象的header中必须含有Access-Control-Allow-origin允许跨域的标识

img

在发送 fetch 请求的时候就会使用到CORS协议,尽管这些对于开发者来说是透明的,但是浏览器还是会发送 origin 字段。

posted @ 2023-06-04 22:16  黄河大道东  阅读(8)  评论(0编辑  收藏  举报