Promise

2020-03-22  本文已影响0人  Lyan_2ab3

为什么需要引入 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

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

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方法是定义在原型对象。

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语句捕获。

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]);

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

  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

如果前面的 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 的微任务注册。

第三道

如果调用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

上一篇下一篇

猜你喜欢

热点阅读