javascript 进阶之 - Promise

引言

promise 主要解决了回调地域, 也就是嵌套太深的 callback, 而采用链式方式. 如:

// 普通方式
$.get({
    url:'url',
    success:function(){
        $.get({
            url:'url2',
            success:function(){
                $.get({
                    url:'url3',
                    success:function(){
                        // ...
                    }
                })
            }
        })
    }
})

// promise , 假如 $.get 支持 promise 方式
$.get({
    url:'url1'
})
.then(res=>{
    return $.get({
        url :'url2'
    })
})
.then(res=>{
    console.log('ok')
})
.catch(()=>{
    console.log('error')
})

Promise 对象

  • 构造 Promise 实例时, 参数为一个函数
var p1 = new Promise(function(resolve,reject){
    console.log('promise start')
})

// Promise 内部类似
function Promise(fn){
    var resolve = function(){}
    var reject = function(){}
    fn(resolve,reject);
    return this;
}
  • then, 收集 resolve 后要执行的回调 ; catch , 收集 reject 后要执行的回调
var p1 = new Promise(function(resolve,reject){
    console.log('promise start')
})

p1.then(function(){
    console.log(1)
})
.then(function(){
    console.log(2)
})

// Promise 内部类似
function Promise(fn){
    var resolve = ()=>{
        this.thenList.forEach((fn)=>{
            fn();
        })
    }
    var reject = ()=>{
        this.catchList.forEach((fn)=>{
            fn();
        })
    }

    this.thenList = [] ;
    this.catchList = [];

    fn(resolve,reject);

    this.then = function(callback){
        this.thenList.push(callback);
        return this;
    }

    this.catch = function(callback){
        this.catchList.push(callback) ;
        return this;
    }

    return this;
}

实践发现 , 并不能打印出 1 , 因为 resolve 的执行, 是早于 then 的调用, 这个时候的 thenList 还是个空数组. 所以修改一下, 先让 then 执行. 也就是利用事件循环的原理.

function Promise(fn){
    var resolve = ()=>{
        setTimeout(()=>{
            this.thenList.forEach((fn) => {
              fn();
            })
        },0)
    }; 

    var reject = ()=>{
        setTimeout(()=>{
            this.catchList.forEach((fn) => {
              fn();
            })
        },0)
    }; 

    // 其他不变

}
  • 而还有一个要注意的点就是 , then 的时候, 是可以返回一个新的 Promise 对象, 打断当前链条的. 而返回其他非 Promise 对 then 的链式无影响.

如下, 并未在返回新的 Promise 未打断原 Promise 的 then 链式:

    var p1 = new Promise(function (resolve, reject) {
      console.log('promise start')
      resolve();
    })
      .then(function () {
        console.log(1)
        return new Promise(function (resolve, reject) {
          reject();
        })
      })
      .then(function () {
        console.log(2)
      })
      .catch(function () {
        console.log('error')
      })
// 应该打印 : promise start , 1 , error

可以通过对每次的 then 和 catch 做返回判断, 如果返回的是 Promise 对象, 则停止之前的 then 和 catch 执行, 将剩余未执行的拼在返回的 Promise 的对象的原有 thenList 和 catchList 后面 .
这里顺便也加上参数和状态.

   function Promise(fn) {
      this.status = 'Pending';

      var resolve = (...args) => {
        this.status = 'Resolved';
        setTimeout(() => {
          for (var i = 0; i < this.thenList.length; i++) {
            var result = this.thenList[i](...args);
            if (result instanceof Promise) {
              result.thenList = [...result.thenList, ...this.thenList.slice(i + 1)]
              result.catchList = [...result.catchList, ...this.catchList]
              break;
            }
          }
        })
      }

      var reject = (...args) => {
        this.status = 'Rejected';
        setTimeout(() => {
          for (var i = 0; i < this.catchList.length; i++) {
            var result = this.catchList[i](...args);
            if (result instanceof Promise) {
              result.thenList = [...result.thenList, ...this.thenList]
              result.catchList = [...result.catchList, ...this.catchList.slice(i + 1)]
              break;
            }
          }
        })
      }

      this.thenList = [];
      this.catchList = [];

      fn(resolve, reject);

      this.then = function (callback) {
        this.thenList.push(callback);
        return this;
      }

      this.catch = function (callback) {
        this.catchList.push(callback);
        return this;
      }

      return this;
    }

需要特别说明的是: 这里虽然借用 setTimeout 实现 , 但 promise 和 setTimeout 在事件循环中的表现还是有差异的 , promise 是生成微任务 jobs, 而 setTimeout 则是生成宏任务 , 也就是 task , 每次的 task 是一个任务 , 一个完整的任务包括 jobs .

async / await

async 和 await 只是 promise 的语法糖, 其核心还是 promise .
既然是糖, 吃多了对牙口不好.

  • await 替代 promise 的 then 的写法

使用 await 可以省略 then 的链式写法 , 但是 await 必须在 async “装饰” 的函数体内. 也就是 await 不能写在全局作用域内.

// 常规方式新建 promise 对象
var p1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{ resolve('hello') },5000)
})

// 常规 promise then 调用
p1.then(result =>{ console.log('normal',result) })

// await 方式全局作用域调用
// var result = await p1; // 报错 Uncaught SyntaxError: await is only valid in async function

// await 正常调用 => 在一个 async 修饰的函数内
async function test(){
    console.log('async start')
    var result = await p1;
    console.log('await',result)
}
test();
console.log('end');

// 打印 async start , end ,  normal hello , await hello

从上述例子, 可以得出以下几点:
1. await 等的是一个 promise 的 resolve 的结果
2. await 只能在一个 async 修饰的函数体内
3. await 时, 不会堵塞 await 所在的 async 的函数体之外的 js 执行

所以通俗 await 做了哪些事

// 常规写法
var result ;
p1.then(res=>{
    result = res
    console.log(result);
})

// await 写法
var result = await p1;
console.log(result);

还有就是 await 可以不只是等 promise , 也可以等一个同步函数

function sayHi(){
    return 'hi';
}

async function test(){
    var result = await sayHi(); 
    console.log(result); 
}
test();
console.log('end');

// 打印结果: end , hi

可以看出, 虽然 await 的是一个同步执行, 直接返回了 hi , 但是却是在 end 后面输出; 由此可以大胆猜测 , await 的作用就是把它后面的代码都放到了 promise 的 then 中

  • async 修饰一个函数返回 promise 对象
// 常规创建 promise 对象
var p1 = new Promise((resolve,reject)=>{
    resolve('hello');
})

// async 创建 promise 对象
// 1. 无返回 , 则是 resolve(undefined)
var p2 = (async function b(){
})();

// 2. 有返回, 则是 resolve(返回值)
var p3 = (async function c(){
    return 'hi';
})();

// 调用
p1.then(res=>{console.log(res)});
p2.then(res=>{console.log(res)});
p3.then(res=>{console.log(res)});

console.log('end');
// 打印 : end , hello , undefined , hi

注意:
1. async 用来 “修饰” 函数, 但该函数并不是 promise , 而是该函数执行后, 返回一个 promise 对象.
2. async “修饰” 的函数有返回值, 则作为 resolve 的参数返回, 无返回值或者无返回, 则 undefined 作为 resolve 的结果返回.

async 和 promise 的 resolve 的一点差别

// 常规 promise 的延时返回
var p1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{ resolve('hi') },5000);
})

// await 如果照常规写法返回
var p2 = (async function b(){
    setTimeout(()=>{ return ('hi')},5000)
})();

p1.then(res=>{ console.log(res) }); // 5 秒后打印 hi
p2.then(res=>{ console.log(res) }); // undefined

// 打印结果: undefined , 5秒后打印 hi

// 那么怎么实现 async 中的 延迟效果? 也就是说 d 函数需要等 (await) 5秒, 再返回出去
var p3 = (async function d(){
    await wait(5000);
    return 'hi';
})();

function wait(s){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{ resolve() },s)
    })
}

p3.then(res=>{ console.log(res) }); 

那么, 从上面可以看出什么端倪?
1. async “修饰” 的函数, 也还是一个同步函数, 也就是它急切地完成, 急切地需要一个结果, 急切地包装成一个 promise 对象并返回. 所有 p2 他不会等异步的定时器走完, 返回 ‘hi’ , 才结束一个函数. 直接跳过异步事件, 走完整个函数, 找到返回值包装返回.
2. async 并不能代替 new Promise 做事情, 主要作用是用来 , 框定一部分异步代码 (await) 逻辑 ,使得其按 promise 的 then 方式顺序执行, 又不至于影响到范围之外的同步代码的执行.

  • await 和 async 总结

    1. await 只能存在 async 修饰的函数体内
    2. await 后面的代码, 都将等待 await 的 promise 的 resolve , 也就是堵塞的.
    3. async 单独用处不大, 主要用来配合 await .

目前综合来看 , await 和 async 的组合的主要作用, 是解决了 then 的存在和 then 的冗长的链式调用. 然后通过局部变量接收 promise 的 then 的结果, 方便后面的调用, 避免了 多个 then 需要传参的尴尬.

await 的错误捕获 和 Promise.all 等后面专门总结一下.

总结

以上 Promise 模拟实现, 并非 js 内置实现的 Promise , 只是用简单的代码模拟 , 强调 Promise 其中的几个特性.

  1. Promise 是一个函数.
  2. Promise 的参数是一个函数 ( fn ), 且在生成 Promise 实例时, 这个函数会立即执行.
  3. Promise 的 then 和 catch 的参数也是函数, 在生成 Promise 实例时, 并不会直接执行函数 , 但是会挂载在 Promise 实例上 .
  4. Promise 传入的 fn 执行 resolve 或 reject 时 , 这时候的 Promise 实例必然已经是初始化完成, 即 then 和 catch 都已经挂载完毕.
  5. 第 4 条 , 也就是说明 resolve 和 reject 触发 的 then 和 catch 在同步代码执行之后 . 而且实际上是在 setTimeout 之前.
  6. 在 then 和 catch 中可以返回一个新的 Promise , 打断之前 Promise 链.

研究新的事物, 可以从两个方面出发:

  1. 用旧有的已经掌握的知识来推导新知识, 以及建立联系
  2. 控制变量法, 每次只研究其中一小部分, 其他保持不变.

相关参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值