Promise

作者: Lyan_2ab3 | 来源:发表于2020-03-22 11:37 被阅读0次

    为什么需要引入 Promise

    我们都知道web 单线程 有很多异步回调,这短短的一段代码里面竟然出现了五次回调,这么多的回调会导致代码的逻辑不连贯、不线性,非常不符合人的直觉,这就是异步回调影响到我们的编码方式。

    
    //执行状态
    function onResolve(response){console.log(response) }
    function onReject(error){console.log(error) }
    
    let xhr = new XMLHttpRequest()
    xhr.ontimeout = function(e) { onReject(e)}
    xhr.onerror = function(e) { onReject(e) }
    xhr.onreadystatechange = function () { onResolve(xhr.response) }
    
    //设置请求类型,请求URL,是否同步信息
    let URL = 'https://time.geekbang.com'
    xhr.open('Get', URL, true);
    
    //设置参数
    xhr.timeout = 3000 //设置xhr请求的超时时间
    xhr.responseType = "text" //设置响应返回的数据格式
    xhr.setRequestHeader("X_TEST","time.geekbang")
    
    //发出请求
    xhr.send();
    

    那么怎么才能 变的更加的线性,我们开始封装异步回调,只在乎输入和输出的结果。

    引入promise 是如何解决消灭嵌套调用和多次错误处理?
    Promise 如何实现了回调函数的延时绑定?
    如何将回调函数 onResolve 的返回值穿透到最外层,摆脱嵌套循环的?
    Promise 出错后,是怎么通过“冒泡”传递给最后那个捕获异常的函数?
    Promise 中为什么要引入微任务?

    promise

    • 1、对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
    • 2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
    const promise = new Promise(function(resolve, reject) {
      // ... some code
    
      if (/* 异步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });
    
    
    • Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
    • resolved 函数是,promise 对象从pending 变为 resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
    • rejected 函数是,promise 对象从pending 变为 rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
    • Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
    promise.then(function(value) {
      // success
    }, function(error) {
      // failure
    });
    
    

    第一个回调函数是Promise对象的状态变为resolved时调用,
    第二个回调函数是Promise对象的状态变为rejected时调用。
    其中,第二个函数是可选的,不一定要提供。

    let promise = new Promise(function(resolve, reject) {
      console.log('Promise');
      resolve();
    });
    
    promise.then(function() {
      console.log('resolved.');
    });
    
    console.log('Hi!');
    
    // Promise
    // Hi!
    // resolved
    // promise 是立即执行的,promise状态进入resolve 状态,
    //则直接将回调放入微任务队列中,执行then 方法是同步的,
    //但是then 中的回调是异步的
    
    new Promise((resolve, reject) => {
      resolve(1);
      console.log(2);
    }).then(r => {
      console.log(r);
    });
    // 2
    // 1
    // 调用resolve或reject并不会终结 Promise 的参数函数的执行。
    //调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。
    

    Promise.prototype.then()

    Promise 实例具有then方法,也就是说,then方法是定义在原型对象。

    • then方法的第一个参数是resolved状态的回调函数,
    • 第二个参数(可选)是rejected状态的回调函数。
    • then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)

    Promise.prototype.catch()

    Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

    const promise = new Promise(function(resolve, reject) {
      throw new Error('test');
    });
    promise.catch(function(error) {
      console.log(error);
    });
    // Error: test
    

    Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

    • catch 返回的仍然是一个promise,后面还可以调用then ,如果catch 中间不抛出错误,就直接跳过。

    Promise.prototype.finally()

    不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数

    finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

    Promise.all()

    Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

    const p = Promise.all([p1, p2, p3]);
    
    
    • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

    • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

    实现一个promise.all
    有时候面试会遇到这样的问题

    • 首先我们要知道 promise.all 返回的是一个promise 实例

    • 如果传入的参数中的 promise 都变成完成状态,Promise.all 返回的 promise 异步地变为完成。

    • 如果传入的参数中,有一个 promise 失败,Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成

    • 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组

      Promise.all = function (promises){
            return new Promise((resolve,reject) => {
              // 将迭代对象转化为数组
              promises = Array.from(promises)
              if(promises.length === 0){
                resolve([]) 
              }else{
                let result = [];
                let index = 0;
                for( let i = 0; i < promises.length; i++){
                  Promise.resolve(promises[i]).then(data=>{
                    result[i] = data;
                    if(++index === promises.length){
                      resolve(result)
                    }
                  },err =>{
                    reject(err)
                    return          
                    })
                }
              }
            })
          }
    
    // 封装 Promise.all方法
    Promise.all = function (values) {
        return new Promise((resolve, reject) => {
            let result = []; // 存放返回值
            let counter = 0; // 计数器,用于判断异步完成
            function processData(key, value) {
                result[key] = value;
                // 每成功一次计数器就会加1,直到所有都成功的时候会与values长度一致,则认定为都成功了,所以能避免异步问题
                if (++counter === values.length) {
                    resolve(result);
                }
            }
            // 遍历 数组中的每一项,判断传入的是否是promise
            for (let i = 0; i < values.length; i++) {
                let current = values[i];
                // 如果是promise则调用获取data值,然后再处理data
                if (isPromise(current)) {
                    current.then(data => {
                        processData(i, data);
                    }, reject);
                } else {
                    // 如果不是promise,传入的是普通值,则直接返回
                    processData(i, current);
                }
            }
        });
    }
    
    

    promise 核心原理解析:

    Promise函数参数可以作为输入信息,而后经过Promise的内部处理

    // 实例化 Promise
    new Promise((resolve, reject)=> {
        // 输入
        AjaxRequest.post({
            url: 'url',
            data: {},
            sueccess: ()=> {
                // resolve
                resolve(res)
            },
            fail: (err)=> {
                // reject
                reject(err)
            }
        })
    }).then((res)=> {
        // res 输出
        // ...操作
    }).catch((err)=> {
        // err 输出
        // ...操作
    })
    

    内部进行了哪些操作呢?

    pending状态下会运行的函数

    1、实例化构造函数

    // 首先运行,Promise构造函数
    function Promise(fn) {
        if (typeof this !== 'object') {
        throw new TypeError('Promises must be constructed via new');
      }
      if (typeof fn !== 'function') {
        throw new TypeError('Promise constructor\'s argument is not a function');
      }
        // _deferreds的类型,1是 single,2是 array
        this._deferredState = 0;
        // 0 - pending
        // 1 - fulfilled(resolved)
        // 2 - rejected
        // 3 - 另一个Promise的状态
        this._state = 0;
        // promise 执行结果
        this._value = null;
        // then注册回调数组
        this._deferreds = null;
        // fn等于noop 即return
        if (fn === noop) return;
        // 接受Promise回调函数 和 this 作为参数
        doResolve(fn, this);
    }
    

    Promise构造函数,会初始化属性,fn就是我们传入的函数。
    doResolve(fn, this);,this指向它自己,负责执行fn函数。
    等下面的then函数和catch函数的回调函数注册完之后,doResolve函数将立即执行

    2、then 方法注册回调函数

    Promise.prototype.then = function(onFulfilled, onRejected) {
        if (this.constructor !== Promise) {
            // safeThen函数也是通过调用handle函数,return 新的Promise对象
            return safeThen(this, onFulfilled, onRejected);
        }
        // 生成新的Promise对象
        var res = new Promise(noop);
        handle(this, new Handler(onFulfilled, onRejected, res));
        return res;
    };
    
    function safeThen(self, onFulfilled, onRejected) {
      return new self.constructor(function (resolve, reject) {
        var res = new Promise(noop);
        res.then(resolve, reject);
        handle(self, new Handler(onFulfilled, onRejected, res));
      });
    }
    
    
    // Handler构造函数
    // 它的作用是挂载 then中的回调函数 和 一个空的Promise对象
    function Handler(onFulfilled, onRejected, promise){
        // then中的Fulfilled回调函数
        this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
        // then中的Rejected回调函数
        this.onRejected = typeof onRejected === 'function' ? onRejected : null;
        // 保存新的Promise
        this.promise = promise;
    }
    
    // 保存then注册回调函数,更新回调函数状态
    function handle(self, deferred) {
          while (self._state === 3) {
           self = self._value;
         }
        if (Promise._onHandle) {
          Promise._onHandle(self);
        }
    
        // pedding 状态 
        if (self._state === 0) {
            // deferred == new Handler(onFulfilled, onRejected, res)
            if (self._deferredState === 0) {
                self._deferredState = 1;
                // 存储then回调deferred对象
                self._deferreds = deferred;
                return;
            }
            if (self._deferredState === 1) {
                self._deferredState = 2;
                // 存储then回调deferred对象
                self._deferreds = [self._deferreds, deferred];
                return;
            }
            // 存储then回调函数对象
            self._deferreds.push(deferred);
            return;
        }
    
        // 只有当进入到非pedding状态,handleResolved才会运行
        handleResolved(self, deferred);
    }
    

    Handler函数生成一个deffer对象,用于保存then函数中的onFulfilled和onRejected回调.以及返回的新的promise实例。
    then方法中的核心函数就是handle函数,它负责接收this和new Handler对象。
    若在pedding状态下,handle函数只负责注册回调函数,更新回调函数状态。在非pedding状态下,则会执行handleResolved函数。

    3、 catch方法注册回调函数

    Promise.prototype['catch'] = function (onRejected) {
      return this.then(null, onRejected);
    };
    // catch方法的回调函数实际是通过then方法来完成保存的。
    
    

    4、调用doResolve函数执行fn

    // 调用doResolve函数
    function doResolve(fn, promise) {
        var done = false;
        
        // tryCallTwo函数执行 类似于
        // (resolve, reject) => {if(err){reject(err);return};resolve(res)}执行;
        var res = tryCallTwo(fn, function (value) {
            if (done) return;
            done = true;
            resolve(promise, value);
        }, function (reason) {
            if (done) return;
            done = true;
            reject(promise, reason);
        });
    
        // fn函数调用失败,手动运行reject函数
        if (!done && res === IS_ERROR) {
            done = true;
            reject(promise, LAST_ERROR);
        }
    }
    
    

    doResolve是同步直接调用传入的函数。
    其中tryCallTwo函数作用是调用函数fn,它接受三个参数。先执行fn函数,根据结果,再执行resolve函数或reject函数。
    在resolve函数或reject函数被调用之前,Promise对象的状态依然是pending。

    promise.png

    进入resolve或reject状态时会运行的函数:

    调用resolve

    若Promise对象的fn函数执行正常,之后就会调用resolve函数

    
    function resolve(self, newValue) {
        // 。。。省略
        
        // newValue存在 & (newValue是一个对象 || newValue是一个函数)
        if (
            newValue &&
            (typeof newValue === 'object' || typeof newValue === 'function')
        ) {
            // 获取then函数
            var then = getThen(newValue);
            // 。。。省略
    
            if (
                then === self.then &&
                newValue instanceof Promise
            ) {
                // 如果newValue 是一个Promise对象,那么调用finale函数
                self._state = 3;
                self._value = newValue;
                finale(self);
                return;
            } else if (typeof then === 'function') {
                // 如果newValue 是一个函数,就继续调用doResolve函数
                doResolve(then.bind(newValue), self);
                return;
            }
        }
        // 标记完成,进入结束流程
        self._state = 1;
        self._value = newValue;
        finale(self);
    }
    
    

    确认newValue的值,如果newValue是一个函数,就继续循环调用doResolve函数;
    如果newValue 是一个Promise对象,那么就直接调用finale函数。
    都不是,则直接调用finale函数。

    调用finale函数

    function finale(self) {
        // 单个回调
        if (self._deferredState === 1) {
            // 执行handle函数,实际是执行handleResolved
            handle(self, self._deferreds);
            self._deferreds = null;
        }
        // 回调数组
        if (self._deferredState === 2) {
            for (var i = 0; i < self._deferreds.length; i++) {
                // 执行handle函数,实际是执行handleResolved
                handle(self, self._deferreds[i]);
            }
            self._deferreds = null;
        }
    }
    
    

    finale函数表示进入结束流程,执行handle函数。
    同时在上面已经说到,在非pedding状态下,执行handle函数,实际会是执行handleResolved函数。

    调用handleResolved函数

    var asap = require('asap/raw');
    
    function handleResolved(self, deferred) {
        asap(function() {
            var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
            // 不存在 onFulfilled & onRejected
            // deferred.promise 只是一个空的Promise对象
            if (cb === null) {
                // 1 - fulfilled(resolved)
                if (self._state === 1) {
                    resolve(deferred.promise, self._value);
                } else {
                    reject(deferred.promise, self._value);
                }
                return;
            }
            // 执行cb回调函数
            var ret = tryCallOne(cb, self._value);
            if (ret === IS_ERROR) {
                // 错误,报reject
                reject(deferred.promise, LAST_ERROR);
            } else {
                resolve(deferred.promise, ret);
            }
        });
    }
    
    

    通过异步asap调用,若不存在onFulfilledonRejected,直接调用resolvereject。若存在,则tryCallOne回调的结果,直接调用resolvereject

    WechatIMG549.png

    Promise 的注册和执行过程

    第一道题

    new Promise((resolve, reject) => {
      console.log("外部promise");
      resolve();
    })
      .then(() => {
        console.log("外部第一个then");
        return new Promise((resolve, reject) => {
          console.log("内部promise");
          resolve();
        })
        .then(() => {
        console.log("内部第一个then");
        })
        .then(() => {
        console.log("内部第二个then");
        });
      })
      .then(() => {
        console.log("外部第二个then");
      });
    
    //  当执行 then 方法时,如果前面的 promise 已经是 resolved 状态,
    // 则直接将回调放入微任务队列中
    

    output:
    外部promise
    外部第一个then
    内部promise
    内部第一个then
    内部第二个then
    外部第二个then

    • 外部第一个 new Promise 执行,执行完 resolve ,所以立即将回调放入微任务队列。所以此时 外部第一个then 放入微任务

    • 外部第一个 then 方法里面 return 一个 Promise,这个 return ,代表 外部的第二个 then 的执行需要等待 return 之后的结果

    • 当然会先执行完内部两个 then 之后,再执行 外部的第二个 then

    如果前面的 promise 已经是 resolved 状态,则会立即将回调推入微任务队列(但是执行回调还是要等到所有同步任务都结束后)

    如果 then 中的回调返回了一个 promise,那么 then 返回的 promise 会等待这个 promise 被 resolve 后再 resolve

    第二道题

    new Promise((resolve, reject) => {
      console.log("外部promise");
      resolve();
    })
      .then(() => {
        console.log("外部第一个then");
        new Promise((resolve, reject) => {
          console.log("内部promise");
          resolve();
        })
          .then(() => {
            console.log("内部第一个then");
          })
          .then(() => {
            console.log("内部第二个then");
          });
      })
      .then(() => {
        console.log("外部第二个then");
      });
    
    // 比上面的代码少一个return
    

    output:
    外部promise
    外部第一个then
    内部promise
    内部第一个then
    外部第二个then
    内部第二个then

    new Promise((resolve, reject) => {
      console.log("外部promise");
      resolve();
    })
      .then(() => {
        console.log("外部第一个then");
        new Promise((resolve, reject) => {
          console.log("内部promise");
          resolve();
        })
          .then(() => {
            console.log("内部第一个then");
          })
          .then(() => {
            console.log("内部第二个then");
          }) .then(() => {
            console.log("内部第3个then");
          })
      })
      .then(() => {
        console.log("外部第二个then");
      })
     .then(() => {
        console.log("外部第3个then");
      })
    

    外部promise
    外部第一个then
    内部promise
    内部第一个then
    外部第二个then
    内部第二个then
    外部第3个then
    内部第3个then

    事件执行机制: 先注册后执行

    *1、 当new Promise 执行碰到resolve 状态确定之后,开始第一个then 的微任务注册。

    • 2、外1then 的回调还没有执行,是pending 状态。所以外2then 的回调也不会被推入微任务队列也不会执行。(外部的第二个 then 的注册,需要等待 外部的第一个 then 的同步代码执行完成

    • 3、在实例化时执行函数,打印 log: 内部promise,然后执行 resolve 函数,接着执行到内部的第一个 then(内1then),由于前面的 promise 已被 resolve,所以将回调放入微任务队列中。

    • 4、内1then 返回的 promise 是 pending 状态 时,内2then和外2 then 不注册不执行。

    • 5、外部1then 执行完,外1then 返回的 promise 的状态由 pending 变为 resolved。同时遍历之前通过 then 给这个 promise 注册的所有回调,将它们的回调放入微任务队列中,也就是外2then 的回调

    • 外部的第一个 then 的同步操作已经完成了,然后开始注册外部的第二个 then,此时外部的同步任务也都完成了。

    • 同步操作完成之后,那么开始执行微任务:
      内部的第一个 then 是优先于外部的第二个 then 的注册,所以会执行完内部的第一个 then 之后;
      然后注册内部的第二个 then ;
      然后执行外部的第二个 then ;
      ,然后再执行内部的第二个 then。

    第三道

    如果调用resolve函数和reject函数时带有参数, 参数会被传递给回调函数,resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例

    const p1 = new Promise(function (resolve, reject) {
      // ...
    });
    
    const p2 = new Promise(function (resolve, reject) {
      // ...
      resolve(p1);
    })
    
    

    这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。

    const p111 = new Promise(function (resolve, reject) {
      // ...
    resolve()
    }).then(()=>{
    console.log('p1 then')
    return 11
    });
    
    const p12 = new Promise(function (resolve, reject) {
      // ...
      resolve(p111);
    }).then((p3)=>{
    console.log('p2 then',p111,p3)
    })
    
    // p1 then   > p2 then Promise {<resolved>: 11} 11
    

    改写函数成promise 形式:

    var fs = require("fs");
     
    function myReadFile(){
        return new Promise(function(resolve,reject){
            fs.readFile("./index.html",function(err,data){
                if (!err) {
                    resolve(data);
                }else{
                    reject(err);
                }
            });
        });
    }
     
    myReadFile().then(function(d){
        console.log(d.toString());
    }).catch();
    
    

    红灯3秒亮一次,绿灯1秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用Promise实现)三个亮灯函数已经存在:

    function red() {
        console.log('red');
    }
    function green() {
        console.log('green');
    }
    function yellow() {
        console.log('yellow');
    }
    

    红灯3秒亮一次,绿灯1秒亮一次 ,黄灯2秒亮一次,意思就是3秒执行一次red函数,2秒执行一次green函数,1秒执行一次yellow函数,不断交替重复亮灯,意思就是按照这个顺序一直执行这3个函数,这步可以利用递归来实现。

    function red() {
        console.log('red');
    }
    function green() {
        console.log('green');
    }
    function yellow() {
        console.log('yellow');
    }
    
    var light = function (timmer, cb) {
        return new Promise(function (resolve, reject) {
            setTimeout(function () {
                cb();
                resolve();
            }, timmer);
        });
    };
    
    var step = function () {
        Promise.resolve().then(function () {
            return light(3000, red);
        }).then(function () {
            return light(2000, green);
        }).then(function () {
            return light(1000, yellow);
        }).then(function () {
            step();
        });
    }
    
    step();
    

    https://es6.ruanyifeng.com/#docs/promise

    https://www.jianshu.com/p/4bb1521343ba

    相关文章

      网友评论

          本文标题:Promise

          本文链接:https://www.haomeiwen.com/subject/ykpjyhtx.html