异步与promise

103 阅读5分钟

什么是异步什么是同步

如果能直接拿到结果,就是同步

  • 比如医院挂号,拿到号才会离开窗口

  • 不拿到结果不会离开

如果不能直接拿到结果,就是异步

  • 比如用餐排队拿号,拿到号以后可以干别的,号到你了才回来用餐

  • 可以每十分钟去问一下(轮询)

  • 也可以扫码用微信接收通知(回调)

异步举例

以AJAX为例

  • request.send() 之后,并不能直接得到response

  • 必须等到readyState值为4后,浏览器才回头调用request.onreadyStateChange

  • 才能得到request.response

  • 跟餐厅给你发消息提醒你该你用餐了是一样的

回调 call back

  • 写给自己用的函数不是回调,写给别人用的函数,就是回调

  • request.onreadyStateChange就是我写给浏览器调用的

  • 中文里,回头也有将来的意思,比如我回头请你吃饭,表示以后请你吃饭

  • 写了却不调用,给别人调用的函数,就是回调

回调举例1

//  把函数1给另一个函数2

    function f1() {}

    function f2(fn) {
        fn()
    }
    
    f2(f1)// 没有直接调用f1,而是把f1传给f2调用,f1就是回调
    

回调举例2

    function f1(x) {
        console.log(x);
    }

    function f2(fn) {
        fn('你好')
    }

    f2(f1)// x可以改成任意名字,x表示第一个参数而已

异步与回调的关系

关联

  • 异步任务需要在得到结果时通知JS来拿结果,问题是怎么通知

  • 可以让JS留一个函数地址(用餐时留下的电话号码)给浏览器

  • 异步任务完成时,浏览器用该函数地址即可(排到你时餐厅拨打你的电话通知你)

  • 同时把结果作为参数传给该函数

  • 这个函数是我写给浏览器调用的,所以是回调函数

区别

  • 异步任务需要用到回调函数来通知结果

  • 但回调函数不一定只用在异步任务里

  • 回调还可以用到同步任务里

  • 比如array.forEach( n => console.log(n)) 就是同步回调

判断同步异步

如果一个函数的返回值处于

  • setTimeout

  • AJAX(即XMLHttpRequest)

  • AddEventListener

  • 这三个东西内部,那么这个函数就异步函数

需要注意

  • 网上说AJAX可以设置为同步的,但只有傻*前端才会这么做

  • 因为如果设置同步的,那么在响应的时候,整个页面会停下来等待响应

摇骰子函数举例

    function 摇骰子() {
        setTimeout(() => {
            return parseInt(Math.random() * 6) + 1
        }, 1000)
        // return undefined
    }

分析

  1. 摇骰子()没有写return,那就是returnundefined

  2. 箭头函数有return,返回真正的结果

  3. 所以这是一个异步函数/异步任务

摇骰子函数怎么拿到异步结果

    const n = 摇骰子()
    console.log(n); // undefined

用回调

    function f1(x) {
        console.log(x)
    }
    摇骰子(f1)

要求摇骰子函数得到结果后把结果作为参数传给f1

function 摇骰子(fn) {
        setTimeout(() => {
            fn(parseInt(Math.random() * 6) + 1)
        }, 1000)
    }
// 简化为箭头函数 由于 f1 声明后只用了一次,所以可以删掉 f1
function f1(x) {
    console.log(x);
摇骰子(f1)

// 改成
摇骰子(x => {
    console.log(x);
})

// 还能简化

摇骰子(console.log)

// 如果参数个数不一致就不能这样简化

总结

  • 异步任务不能拿到结果

  • 于是我们传一个回调给异步任务

  • 异步任务完成时调用回调

  • 调用的时候把结果作为参数

如果异步任务有两个结果:成功或失败怎么办?

方法1:回调接受两个参数 node.js就这么做的

fs.readFile('./1.txt', (error, data) => {
        if (error) {
            console.log('失败');
            return
        }
        console.log(data.toString()); //成功
    })

方法2:搞两个回调

// 可以这样
ajax('get', '/1.json', data(() => {}), error(() => {}))
// 前面函数是成功回调,后面函数是失败回调
// 还可以这样
ajax('get', '/1.json', {
    success: () => {},
    fail: () => {}
}) // 接受一个对象,对象有两个Key 表示成功和失败

这些方法的不足(为什么要用Promise)

  • 不规范,命名五花八门,每个程序员使用的名称可能都不一样

  • 容易出现回调地狱,代码变得看不懂,比如下面代码这样

getUser(user => {
    getGroups(user, (groups) => {
        groups.forEach((g) => {
            g.filter(x => x.ownerId === user.id).forEach(x => console.log(x))
        })
    })
}) 
  • 或这样

607c117ceed54a0d3005d0f085a2ae6.png

  • 还很难进行错误处理

解决方法

  • 用Promise

Promise

  • 为了解决上述三个问题,前端程序员找到了Promise思想

  • 1976年,丹尼尔和大卫提出的Promise

  • 后人基于这个思想发明了Future、Delay、Deferred等

  • 前端结合Promise和JS制定了 Primise/A+规范

以AXAJ封装为例

  • 传统封装
ajax = (method, url, options) => {
    const {
        success,
        fail
    } = options // 析构赋值
    // const success = options.success
    // const fail = options.fail
    const request = new XMLHttpRequest
    request.open(method, url)
    request.onreadystatechange = () => {
        if (request.readyState === 4) {
            //成功就调用 success,失败就调用 fail
            if (request.status < 400) {
                success.call(null, request.response)
            } else if (request.status >= 400) {
                fail.call(null, request, request.status)
            }
        }
    }
    request.send()
  • 传统调用
ajax('get', '/xxx', {
    success(response) {},
    fail: (request, status) => {}
}) // success 是 function 缩写 ,右边是箭头函数
// 用到了两个回调,还是使用了 success 和 fail

Promise说这代码太傻了,所以我们改成Promise的写法

//封装
ajax = (method, url, options) => {
    return new Promise((resolve, reject) => {
        const {
            success,
            fail
        } = options
        const request = new XMLHttpRequest
        request.open(method, url)
        request.onreadystatechange = () => {
            if (request.readyState = 4) {
                // 成功就调用resolve,失败就调用reject
                if (request.status < 400) {
                    resolve.call(null, request.response)
                } else if (request.status >= 400) {
                    reject.call(null, request)
                }
            }
        }
        request.send()
    })
}

//调用
ajax('get', '/xxx').then((response) => {}, (request) => {}) 
  • 虽然也是回调,但是不需要记 success 和 fail 了

  • then 的第一个参数就是 success,then 的第二个参数就是 fail

  • ajax()返回了一个含有 .then() 方法的对象

学习Promise

  • 背五个单词

  • return new Promise ((resolve , reject) => {})

  • 用熟练它们

总结

第一步

  • return new Promise ((resolve , reject) => {})

  • 任务成功则调用resolve(result)

  • 任务失败则调用reject(error)

  • resolvereject 会再去调用成功和失败函数

第二步

  • 使用 .then(success,fail) 传入成功和失败函数

第三步

  • 以后学习到promise更高级的用法时再补充

axios

刚才封装的ajax有很多缺点

  • post无法上传数据 request.send(这里可以上传数据)

  • 不能设置请求头 request.setRequestHeader(key,value)

怎么解决

  • 花时间把ajax写到完美(主要现在没这么多时间)

  • 使用 jQuery.ajax (过时的API,但可以学习使用,看看文档)

  • 使用 axios (这个库是目前主流的库)

axios介绍

  • 目前最新的AJAX库

  • 抄袭了 jQuery 的封装思路

  • 通过大佬的博客Axios 作弊表可以快速了解axios的用法

代码示例

axios.get('/5.json')
    .then(response =>
        console.log(response)
    )