ajax和Promise

文章详细介绍了JavaScript中的回调函数及其可能导致的回调地狱问题,接着讲解了Promise作为解决方案的用法,包括构造函数、状态管理以及then和catch方法。进一步,文章引入了ES7的async/await语法,作为回调地狱的终极解决方案。最后,讨论了JavaScript中的跨域问题,包括同源策略、CORS、JSONP以及代理服务器的解决方案。
摘要由CSDN通过智能技术生成
一、回调函数

概念:将一个函数当做参数传入另一个函数的时候,这个函数就叫回调函数。

当我们需要在异步代码执行结束再执行一些同步代码的时候,在异步代码外面无法预知异步代码什么时候执行结束,只有异步代码内部才能知道异步代码什么时候执行结束。此时,可以将需要执行的同步放在一个函数,将函数当做参数传递进异步代码中,异步代码执行结束后,调用这个函数即可。

二、回调地狱

在一些比较复杂的业务逻辑中,有多个异步代码需要按照一定的顺序执行时,就需要有多个回调函数嵌套在一起执行,造成了后期这个代码难以阅读和维护,这就是回调地狱。

es6为了解决这个问题,所以新增了promise语法。

三、Promise

es6提供的promise是一个构造函数,实例化的时候需要传递一个函数

// 语法:
var p = new Promise(() => {})
// 实例化得到的promise对象,处于pending状态,表示正在处理,没有结果。

promise对象共有3种状态:

pending - 正在处理中

fulfilled - 成功的

rejected - 失败的

传递进去的回调函数,在promise构造函数内部调用的时候,给传递了两个实参,分别是:resolve和reject。这两个参数都是函数:

var p = new Promise((resolve, reject) => {
    console.log(resolve, reject)
    //调用resolve可以将promise对象的状态改为成功,成功的值是resolve的参数;
    resolve('200')
    //调用reject可以将promise对象的状态改为失败,失败的值是reject的参数;
    reject('400')
}
//resolve和reject不会同时执行,因为promise的状态,从pending到成功或失败就已经结束了,不会再次发生改变了。

如果要用成功或失败的值做进一步的操作,正常是需要在回调函数中进行的,但promise的出现的目的就是为了解决嵌套的,所以promise对象提供了两个方法,分别是then和catch。

//在回调函数内部调用resolve,可以通过调用promise对象的then方法获取到成功的值;
var p = new Promise((resolve, reject) => {
    resolve('200')
})
p.then(res => {
    console.log(res)
})
//在回调函数内部调用reject,可以通过调用promise对象的catch方法获取到失败的值:
var p = new Promise((resolve, reject) => {
    reject('400')
})
p.catch(err => {
    console.log(err)
})

promise基于这种嵌套较多的情况,提供了链式调用:变成不嵌套的链式结构:

var p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve(1)
    }, 1000)
})

p1.then(res => {
    console.log(res);
    var p2 = new Promise(resolve => {
        setTimeout(() => {
            resolve(2)
        }, 1000)
    })
    return p2
}).then(res => {
    console.log(res);
    var p3 = new Promise(resolve => {
        setTimeout(() => {
            resolve(3)
        }, 1000)
    })
    return p3
// 如果需要处理错误,可以在最后一个then的后面添加唯一的catch即可解决所有promise对象错误:
}).catch(err => {
    console.log(err)
})
// 只要有一个promise执行错误了,就不会他的then方法,也就不会有其他的promise对象了,所以跳过所有then直接执行到最后的catch了。
// promise的then方法和catch方法是异步代码

promise封装ajax:

function promiseAjax(obj) {
    return new Promise((resolve, reject) => {
        if(obj.url === undefined) {
            throw new Error('请求地址不能为空!')
        }
        if(typeof obj.url != 'string') {
            throw new Error('请求地址不正确!')
        }
        if(obj.method === undefined) {
            obj.method = 'get'
        }
        if(obj.method.toLowerCase() != 'get' && obj.method.toLowerCase() != 'post') {
            throw new Error('请求方式必须是get或post')
        }
        if(obj.async === undefined) {
            obj.async = true
        }
        if(typeof obj.async != 'boolean') {
            throw new Error('async必须是布尔值!')
        }
        if(obj.data != undefined) {
            var data = ''
            if({}.toString.call(obj.data) === '[object String]') {
                if(!obj.data.includes('=')) {
                    throw new Error('字符串数据格式:键=值!')
                }
                data = obj.data
            } else if({}.toString.call(obj.data) === '[object Object]') {
                var arr = []
                for(var key in obj.data) {
                    arr.push(key + '=' + obj.data[key])
                }
                data = arr.join('&')
            } else {
                if(!obj.data.includes('=')) {
                    throw new Error('数据必须是字符串或独享!')
                }
            }
            if(obj.method.toLowerCase() === 'get') {
                obj.url += '?' + data
            }
        }
        if(obj.dataType === undefined) {
            obj.dataType = 'json'
        }
        var xhr = new XMLHttpRequest
        xhr.open(obj.method, obj.url, obj.async)
        if(obj.headers != undefined) {
            if({}.toString.call(obj.header) != '[object Object]') {
                throw new Error('headers头信息必须是对象!')
            }
            for(var key in obj.headers) {
                xhr.setRequestHeader(key, obj.headers[key])
            }
        }
        if(obj.method.toLowerCase() === 'post' && data != undefined) {
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
            xhr.send(data)
        } else {
            xhr.send()
        }
        if(obj.async === true) {
            xhr.onreadystatechange = function() {
                console.log(xhr.readyState);
                if(xhr.readyState === 4) {
                    if(xhr.status >= 200 && xhr.status < 300) {
                        switch(obj.dataType) {
                            case 'json':
                                var res = xhr.responseText
                                res = JSON.parse(res)
                            break
                            case 'text':
                                var res = xhr.responseText
                            break
                            case 'xml':
                                var res = xhr.responseXML
                            break
                            default:
                                throw new Error('dataType必须是json或text或xml!')
                        }
                        resolve(res)
                    } else {
                        reject()
                    }
                }
            }
        } else {
            switch(obj.dataType) {
                case 'json':
                    var res = xhr.responseText
                    res = JSON.parse(res)
                break
                case 'text':
                    var res = xhr.responseText
                break
                case 'xml':
                    var res = xhr.responseXML
                break
                default:
                    throw new Error('dataType必须是json或text或xml!')
            }
            resolve(res)
        }
    })
}
四、async/await

promise虽然将嵌套结构变成了链式结构,但将所有代码作为一个整体,更不利于后期代码的维护,所以在es7中,新增了async/await语法,彻底解决回调地狱,号称回调地狱的终极解决方案。

async/await语法中,其实就是两个关键字:async和await。

  • async:用于修饰一个函数,async单独使用,函数的使用跟正常的函数没有区别,但会隐形返回一个状态为成功的promise对象

async function fn() {
    console.log(1)
}

var p = fn()
console.log(p) // Promise {<fulfilled>: undefined}
//成功值默认为undefined,若要改变,需要在函数中返回return内容。
  • await:await关键字,无法单独使用,必须在async修饰的函数内部使用,用于修饰一个promise对象。

作用是让后续的代码,等待promise有了成功的结果后,再执行,无论promise中执行的是同步代码还是异步代码

async function fn() {
    await new Promise(resolve => {
        setTimeout(() => {
            console.log(111);
            // resolve(222) // 不调用resolve就不会执行后续的3333
        }, 1000)
    })
    console.log(333);
}

fn()
  • 面试题-谈谈你对 async 及 await 的使用理解?

在函数前面加上async关键字,表示函数是异步的,异步函数意味着该函数的执行不会阻塞后面代码的执行;await 用于等待一个异步方法执行完成, await关键字只能放到async函数里面,async 函数返回的是一个promise 对象,正常情况下,await命令后面是一个 Promise对象,返回该对象的结果。如果不是 Promise对象,就直接返回对应的值;不管await后面跟着的是什么,await都会阻塞后面的代码(即加入微任务队列),先执行async函数外的同步代码,等到同步代码执行完,再回到 async 函数中,再执行之前阻塞的代码

async/await的作用就是使异步操作以同步的方式去执行

五、跨域
  1. 跨域介绍

  • 在前后端分离开发模式下,前端和后端同时开发,前端项目需要通过服务器打开,后端接口也是一个服务器。

  • 我们直接在前端项目通过ajax请求到后端的接口,是不能成功的。

  • 浏览器为了安全,对前端的ajax请求进行了限制 - 同源策略。

  • 同源策略:前端ajax所在页面地址跟被请求的服务器地址,需要同源才可以请求成功,否则浏览器不允许请求成功。

  • 同源:同协议且同域名且同端口号,三者只要有一个不同,就是不同源,也就是造成了跨域。

  • 若非要进行跨域,需要使用特殊的解决办法:proxy、cors、jsonp

  1. proxy:服务器代理

解决原理:浏览器能限制客户端的ajax,但是无法限制服务器。前端ajax将请求发送到自己所在服务器中,服务器代替ajax请求目标服务器,将请求回来的数据响应给前端ajax。

在nodejs中服务器中,实现proxy服务器代理需要依赖第三方模块 - http-proxy-middleware

const express = require('express')
const {createProxyMiddleware} = require('http-proxy-middleware')

// 配置代理
const proxy = createProxyMiddleware('/crossReq', {
    target: 'http://localhost:3001/cross',
    changeOrigin: true,
    pathRewrite: {
        '/crossReq': ''
    }
})

const app = express()
app.listen(3000)

app.use(express.static('public'))

app.get('/crossReq', (req, res) => {
    proxy(req, res)
})
  1. cors

浏览器之所以不让前端ajax跨域成功,是因为他不知道目标服务器是否允许不同源的地址请求。如果目标服务器摆明了允许不同源的地址请求,浏览器也没有借口继续拦截。

cors跨域解决办法就是在目标服务器中设置允许不同源地址跨域请求的响应头。

res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000')
// 响应头的值,是允许的来源协议、域名、端口号,*代表任意来源
  1. jsonp

浏览器会限制前端ajax,但前端的标签浏览器是不做限制的。例如:img引入其他服务器的图片地址、link引入其他服务器的css文件地址、script引入其他服务器的js文件地址、iframe引入其他页面地址。

jsonp跨域的原理,就是利用script标签发送请求,不受浏览器限制。

var script=document.createElement('script');
// 根据后端设置请求地址
script.setAttribute('src',"目标服务器地址");
$('head').append(script);
script.parentNode.removeChild(script);
六、事件循环

事件循环,指的是js执行同步代码和异步代码的流程。

同步代码放在执行栈中立即执行,异步代码推送给任务队列,然后执行栈会在任务队列里找有没有异步任务执行,如果有,就将异步任务推送给执行栈执行,反复循环,直到任务队列里的异步任务全部执行完。从执行栈到任务队列里反复查找的过程就叫做事件循环。

简单来说就是执行栈执行完毕,会去任务队列看是否有异步任务,有就送到执行栈执行,反复循环查看执行,这个过程就是事件循环

  • 在处理异步的时候,会将异步代码分为两种:宏任务和微任务。

  • 宏任务包括:定时器

  • 微任务包括:promise的then

因为异步代码有两种,所以异步代码待到要执行的时机时,放任务的队列也分为两种:宏队列和微队列。

js调用栈将所有同步代码执行结束后,会交替清空微队列和宏队列,不停的交替下去,所以叫做事件循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值