「JS笔记」我对promise的理解 V08

371 阅读7分钟

Promise 的用途

同步

  • 直接能拿到结果

异步

  • 不能直接拿到结果

以AJAX的封装为例

        ajax = (method, url, options) => {
            const{success, fail} = options
            const request = new XMLHttpRequest()
            request.open(method, url)
            request.onreadystatechange = () => {
                if (request.readyState === 4) {
                    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(reponse){}, fail:(request, status) => {}
        })
  • request.send()之后,并不能直接得到response
  • 必须等到 readyState 变为4后,浏览器request.onreadystatechange函数,我们才能得到requst.reponse

回调

  • 写给自己用的函数,不是回调
  • 写给别人用的函数,就是回调
  • request.onreadystatechange 就是我写给浏览器调用的
  • 意思就是你浏览器回头调用一下这个函数
  • 回头调用 => 回头请你 写了却不调用,给别人调用的函数

回调和异步的关系

关联

  • 异步任务需要在得到结果时通知JS来拿结果
  • 怎么通知?
  • 可以让JS留一个函数地址(电话号码)给浏览器
  • 异步任务完成时浏览器调用该函数地址即可(拨打电话)
  • 同时把结果作为参数传给该函数(电话里说可以来吃了)
  • 这个函数是我写给浏览器调用的,所以时回调函数 区别
  • 异步任务需要用到回调函数来通知结果
  • 但回调函数不一定只用在异步任务里
  • 回调可以用到同步任务里
  • array.forEach(n => console.log(n))就是同步回调

如何知道一个函数是同步还是异步

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

  • setTimeout
  • AJAX(既 XMLHttpRequest)
  • AddEventListener 这三个东西内部,那么这个函数就是异步函数
    AJAX可以设置同步的???如果设置成同步的那么会导致请求期间页面卡住

如果异步任务两个结果成功或者失败呢?

两个结果

  1. 回调接受两个参数
        fs.readFile('./1.txt', (error, data) => {
            if(error) {
                console.log('失败'); 
                return
            }
            console.log(data.toString()) // 成功
        })
  1. 使用1两个回调
        ajax('get', '/1.json', data() => {}, error() => {})
        // 前面函数是成功回调,后面函数是失败回调
        ajax('get', '/1.json', {
            success: ()={}, fail: ()=>{}
        })
        // 接受对象,对象有两个key表示成功和失败

不足之处

  1. 不规范,名称五花八门,有人用success + error,有人用success + fail,有人用done + fail
  2. 容易出现回调地狱,代码变得看不懂
  3. 很难进行错误处理 回调地狱举例:
        getUser(user =>{
            getGroup(user, (groups) => {
                groups.forEach((g) => {
                    g.filter(x => x.ownerId === user.id)
                     .forEach(x => console.log(x))
                })
            })
        })

那么该如何解决呢?

  • 规范回调的名字或者顺序
  • 拒绝回调地狱,让代码可读性更强
  • 很方便地捕获错误 其实
    早在1976年,Daniel P.FriedmanDavid wose,两人就提出了Promise思想,后人基于此发明了Future、Delay、Deferred
    前端结合 Promise 和 JS,制定了 Promise/A+ 规范 该规范详细描述了Promise的原理和方法
    我们还是以AJAX的封装为例
        ajax = (method, url, options) => {
            const{success, fail} = options
            const request = new XMLHttpRequest()
            request.open(method, url)
            request.onreadystatechange = () => {
                if (request.readyState === 4) {
                    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(reponse){}, fail:(request, status) => {}
        })

改成Promise写法

// 先改调用的姿势
        ajax('get', '/xxx', {
            success(response){}, fail: (request, status) => {}
        })
// 上面用了两个回调,还是用了 success 和 fail
   
// 改成Promise写法
         ajax('get', '/xxx')
             .then(response) => {}, (requset) => {})

虽然也是回调,但是不需要记 success 和 fail
then 的第一个参数就是 success
then 的第二个参数就是 fail
请问 ajax 返回了什么???

  • 返回了一个含有 .then()方法的对象 如何得到?
  • 得要改造ajax源码
        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) {
                        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) => {}, (requset) => {})

如何创建一个 new Promise

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

如何使用 Promise.prototype.then

then()  方法返回一个 Promise (en-US)。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。

        p.then(onFulfilled[, onRejected]);

        p.then(value => {
          // fulfillment
        }, reason => {
          // rejection
        });

  • onFulfilled 可选

  • 当 Promise 变成接受状态(fulfilled)时调用的函数。该函数有一个参数,即接受的最终结果(the fulfillment  value)。如果该参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数

  • onRejected 可选

  • 当 Promise 变成拒绝状态(rejected)时调用的函数。该函数有一个参数,即拒绝的原因(rejection reason)。  如果该参数不是函数,则会在内部被替换为一个 "Thrower" 函数 (it throws an error it received as argument)。

返回值

当一个 Promise 完成(fulfilled)或者失败(rejected)时,返回函数将被异步调用(由当前的线程循环来调度完成)。具体的返回值依据以下规则返回。如果 then 中的回调函数:

  • 返回了一个值,那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
  • 没有返回任何值,那么 then 返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined
  • 抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
  • 返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
  • 返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
  • 返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。

如何使用 Promise.all

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。 Promise.all 等待所有都完成(或第一个失败)。

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([p1, p2, p3]).then(values => {
  console.log(values); // [3, 1337, "foo"]
});

如果参数中包含非 promise 值,这些值将被忽略,但仍然会被放在返回数组中(如果 promise 完成的话)

// this will be counted as if the iterable passed is empty, so it gets fulfilled
var p = Promise.all([1,2,3]);
// this will be counted as if the iterable passed contains only the resolved promise with value "444", so it gets fulfilled
var p2 = Promise.all([1,2,3, Promise.resolve(444)]);
// this will be counted as if the iterable passed contains only the rejected promise with value "555", so it gets rejected
var p3 = Promise.all([1,2,3, Promise.reject(555)]);

// using setTimeout we can execute code after the stack is empty
setTimeout(function(){
    console.log(p);
    console.log(p2);
    console.log(p3);
});

// logs
// Promise { <state>: "fulfilled", <value>: Array[3] }
// Promise { <state>: "fulfilled", <value>: Array[4] }
// Promise { <state>: "rejected", <reason>: 555 }

Promise.all 的异步和同步

// we are passing as argument an array of promises that are already resolved,
// to trigger Promise.all as soon as possible
var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];

var p = Promise.all(resolvedPromisesArray);
// immediately logging the value of p
console.log(p);

// using setTimeout we can execute code after the stack is empty
setTimeout(function(){
    console.log('the stack is now empty');
    console.log(p);
});

// logs, in order:
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: Array[2] }

Promise.all 的快速返回失败行为

var p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 3000, 'three');
});
var p4 = new Promise((resolve, reject) => {
  setTimeout(resolve, 4000, 'four');
});
var p5 = new Promise((resolve, reject) => {
  reject('reject');
});

Promise.all([p1, p2, p3, p4, p5]).then(values => {
  console.log(values);
}, reason => {
  console.log(reason)
});

//From console:
//"reject"

//You can also use .catch
Promise.all([p1, p2, p3, p4, p5]).then(values => {
  console.log(values);
}).catch(reason => {
  console.log(reason)
});

//From console:
//"reject"

如何使用 Promise.race

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。

如果传的迭代是空的,则返回的 promise 将永远等待。

如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。

也就是说Promse.race就是赛跑的意思,Promise.race([p1, p2])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  },1000)
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('failed')
  }, 500)
})

Promise.race([p1, p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)  // 打开的是 'failed'
})

参考文献