大前端课程学习体会与知识总结——02JavaScript异步编程

大前端课程学习体会与知识总结——02JavaScript异步编程

文章内容输出来源:拉勾教育大前端高薪训练营

学习的时候才会发现诱惑实在是多,原本是周四就能理完这篇,没想到拖到了周日晚上,这会精力略有不足,话不多说,直接上干货好了。

今天是异步编程的高光时刻,来吧,先收藏一下,以免文章太长读不完以后找不到~

JavaScript异步编程

采用单线程模式工作的原因

最早js语言就是运行在浏览器端的语言,目的是为了实现页面上的动态交互。实现页面交互的核心就是DOM操作,这就决定了它必须使用单线程模式,否则就会出现很复杂的线程同步问题。

假设在js中有多个线程一起工作,其中一个线程修改了这个DOM元素,同时另一个线程又删除了这个元素,此时浏览器就无法明确该以哪个工作线程为准。所以为了避免线程同步的问题,从一开始,js就设计成了单线程的工作模式。

所以,js执行环境中负责执行代码的线程只有一个。

一个人执行一个任务,如果有多个任务,那任务需要排队。让这个人一个一个去执行。

单线程的优势和弊端

这种模式最大的优势就是更安全,更简单,缺点也很明确,就是如果中间有一个特别耗时的任务,其他的任务就要等待很长的时间,出现假死的情况。

console.log('foo');
for(let i = 0; i< 100000; i++) {
     
  console.log('耗时结束');
}
console.log('等待耗时操作结束');

为了解决这种问题,js有两种任务执行的模式:同步模式(Synchronous)和异步模式(Asynchronous)

异步编程的内容概要

  • 同步模式与异步模式
  • 事件循环与消息队列(js如何实现异步模式)
  • 异步编程的几种方式
  • Promise异步方案、宏任务/微任务队列
  • Generator异步方案、Async/Await语法糖

同步模式与异步模式

同步模式

同步模式:指的是代码的任务依次执行,后一个任务必须等待前一个任务结束才能开始执行。程序的执行顺序和代码的编写顺序是完全一致的。在单线程模式下,大多数任务都会以同步模式执行,同步指的不是同时执行,而是排队执行。

console.log('global begin');

function bar () {
     
  console.log('bar task');
}

function foo () {
     
  console.log('foo task');
  bar()
}

foo()

console.log('global end');

// global begin
// foo task
// bar task
// global end

js在执行引擎当中,维护了一个正在工作的工作表,或者说正在执行的工作表,里面会记录一些当前正在做的一些事情,当这个工作表中所有的任务全部执行完毕,被清空后,那这一轮的工作就算是结束了。

这是一个纯同步模式下的执行情况,整个执行过程非常符合阅读和思考逻辑,但是这种排队执行的机制存在一个很严重的问题,如果其中的某个任务或某行代码执行的时间过长,那么它后面的任务就会出现延迟,这种延迟被称为阻塞,这种阻塞会导致页面卡顿或卡死。

为了避免耗时函数让页面卡顿和假死,所以还有异步模式。

例如:ajax操作或nodejs中的大文件读写,都需要使用异步模式,避免卡死现象

异步模式

异步模式不会去等待这个任务的结束才开始下一个任务,对于耗时操作,都是开启过后就立即往后执行下一个任务。耗时函数的后续逻辑会通过回调函数的方式定义。在内部,耗时任务完成过后就会自动执行传入的回调函数。

异步模式对于JavaScript语言非常重要,没有它就无法同时处理大量的耗时任务。对于开发者而言,单线程下面的异步最大的难点就是代码执行的顺序混乱。

console.log('global begin');

setTimeout(function timer1() {
     
  console.log('timer1 invoke');
}, 1800)

setTimeout(function timer2() {
     
  console.log('timer2 invoke');

  setTimeout(function inner() {
     
    console.log('inner invoke');
  }, 1000)
}, 1000)

console.log('global end');

// global begin
// global end
// timer2 invoke
// timer1 invoke
// inner invoke

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fJiGly4R-1616946983267)(./image/JavaScript异步编程/02-async-mode01.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQPfpHhO-1616946983277)(./image/JavaScript异步编程/02-async-mode02.png)]

js线程某个时刻发起了一个异步调用,它紧接着继续执行其他的任务,此时异步线程会单独执行异步任务,执行过后会将回调放到消息队列中,js主线程执行完任务过后会依次执行消息队列中的任务。这里强调,js是单线程的,浏览器不是单线程的,有一些API是有单独的线程去做的。

这里的同步和异步不是指写代码的方式,而是运行环境提供的API是以同步或异步模式的方式工作。

同步模式API和异步模式API的特点

同步模式API的特点就是任务执行完代码才会继续往下走,例如:console.log

异步模式API的特点就是下达这个任务开启的指令之后代码就会继续执行,代码不会等待任务的结束,例如setTimeOut

回调函数 —— 所有异步编程方案的根基

回调函数:由调用者定义,交给执行者执行的函数

function foo (callback) {
     
  setTimeout(function () {
     
    callback()
  }, 3000)
}

foo(function () {
     
  console.log('这就是一个回调函数');
  console.log('调用者定义这个函数,执行者执行这个函数');
  console.log('其实就是调用者告诉执行者异步任务结束后应该做什么');
})

还有其他的一些实现异步的方式,例如:事件机制和发布订阅。这些也都是基于回调函数之上的变体。

Promise —— 一种更优的异步编程统一方案

Promise概述

虽然回调函数是所有异步编程方案的根基,但是如果我们直接使用传统回调方式去完成复杂的异步流程,就会无法避免大量的回调函数嵌套,导致回调地狱的问题。

// 回调地狱
$.get('/url1', function (data1) {
     
  $.get('/url2', data1, function (data2) {
     
    $.get('/url3', data2, function (data3) {
     
      $.get('/url4', data3, function (data4) {
     
        console.log(data4);
      })
    })
  })
})

为了避免这个问题,CommonJS社区提出了Promise的规范,ES6中称为语言规范。

Promise是一个对象,用来表述一个异步任务执行之后是成功还是失败。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YbJnkQSb-1616946983279)(./image/JavaScript异步编程/04-promise-sample01.png)]

Promise基本用法

// Promise 基本示例

// promise其实是ECMAScript2015提供的全局类型,先构造一个promise实例(创建一个新的承诺)
const promise = new Promise(function (resolve, reject) {
     
  // 需要接收一个函数参数(这里用于"兑现"承诺),着函数会在构造promise的过程中同步执行,这个函数接收两个函数参数resolve, reject
  // resolve是将promise的状态修改为fulfilled,也就是成功,一般将异步操作的结果通过resolve的参数传递出去
  // reject是将promise的状态修改为rejected,也就是失败,一般将异失败的原因通过reject的参数传递出去,表示为什么失败
  // promise的状态一旦确定就不能再修改,所以只能调用两者中的一个

  // resolve(100) // 承诺达成
  reject(new Error('promise rejected')) // 承诺失败
})


promise.then(function (value) {
     
  // 成功后的回调函数
  console.log('resolved', value);
}, function (error) {
     
  // 失败后的回调函数
  console.log('rejected', error);
})

// 即便promise当中没有任何异步操作,then方法中所指定的回调函数仍然会进入到回调队列当中排队,也就是说,必须等待同步代码执行结束后才会执行

// 会先打印end
console.log('end');

返回resolve

const promise = new Promise(function (resolve, reject) {
     
  resolve(100) // 承诺达成
})

promise.then(function (value) {
     
  // 成功后的回调函数
  console.log('resolved', value);
}, function (error) {
     
  // 失败后的回调函数
  console.log('rejected', error);
})

返回reject

const promise = new Promise(function (resolve, reject) {
     
  reject(new Error('promise rejected')) // 承诺失败
})

promise.then(function (value) {
     
  // 成功后的回调函数
  console.log('resolved', value);
}, function (error) {
     
  // 失败后的回调函数
  console.log('rejected', error);
})

即便promise中没有任何的异步操作,then方法的回调函数依然会进入到事件队列中排队。

const promise = new Promise(function (resolve, reject) {
     
  reject(new Error('promise rejected')) // 承诺失败
})

promise.then(function (value) {
     
  // 成功后的回调函数
  console.log('resolved', value);
}, function (error) {
     
  // 失败后的回调函数
  console.log('rejected', error);
})

// 会先打印end
console.log('end');

Promise案例

使用Promise去封装一个ajax的案例

// Promise 方式的 Ajax

function ajax (url) {
     
  return new Promise(function (resolve, reject) {
     
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)  // 设置请求方式及地址
    xhr.responseType = 'json'  // 响应类型为json,H5新特性
    // onload也是H5新特性
    xhr.onload = function () {
     
      // 请求完成后
      if (this.status === 200) {
     
        // 请求成功
        resolve(this.response)
      } else {
     
        // 请求失败
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

ajax('/api/users.json').then(function (res) {
     
  console.log(res);
}, function (error) {
     
  console.log(error);
})

Promise的本质

本质上也是使用回调函数的方式去定义异步任务结束后所需要执行的任务。这里的回调函数是通过then方法传递过去的。

ajax('/api/users.json').then(function onFulfilled (value) {
     
  console.log('onFulfilled', value);
}, function onRejected (error) {
     
  console.log('onRejected', error);
})

Promise链式调用

常见误区

  • 嵌套使用的方法是使用Promise最常见的误区。要使用promise的链式调用的方法尽可能保证异步任务的扁平化。

// Promise 常见误区

function ajax (url) {
     
  return new Promise(function (resolve, reject) {
     
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url) 
    xhr.responseType = 'json'
    xhr.onload = function () {
     
      if (this.status === 200) {
     
        resolve(this.response)
      } else {
     
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

ajax('/api/urls.json').then(function (urls) {
     
  ajax('/api/users.json').then(function (users) {
     
  
  })
})

链式调用的理解

// Promise 链式调用

function ajax (url) {
     
  return new Promise(function (resolve, reject) {
     
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url) 
    xhr.responseType = 'json'
    xhr.onload = function () {
     
      if (this.status === 200) {
     
        resolve(this.response)
      } else {
     
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

// const promise = ajax('/api/users.json')

// const promise2 = promise.then(
//   function onFulfilled (value) {
     
//     console.log('onFulfilled', value);
//   }, 
//   function onRejected (error) {
     
//     console.log('onRejected', error);
//   }
// )

// console.log(promise2 === promise);
// => false

const promise = ajax('/api/users.json')

// 不断链式调用then,每一个then方法,都在为上一个then方法返回的promise对象添加状态明确后的回调,这些promise会依次执行,这里添加的回调函数也是从前到后依次执行
// 也可以在then的回调当中手动返回一个promise对象,下一个then方法就是它的回调,这样就可以避免不必要的回调嵌套了
// 如果返回的不是promise,而是一个普通的值,这个值就会作为当前then方法返回的promise中的值,在下一个then方法中,接收到的参数实际就是这个值
// 如果没有返回值,默认返回undefined
ajax('/api/users.json')
  .then(function (value) {
     
    console.log(value);
    return ajax('/api/users.json')
  })
  .then(function (value) {
     
    console.log(value);
    return 'foo'
  })
  .then(function (value) {
     
    console.log(value);
  })
  .then(function (value) {
     
    console.log(value);
  })
  • promise对象then方法,返回了一个全新的promise对象。可以再继续调用then方法,如果return的不是一个promise对象,而是一个值,那么这个值会作为resolve的值传递,如果没有值,默认是undefined
  • 后面的then方法就是再为上一个then返回的Promise注册回调
  • 前面then方法中回调函数的返回值会作为后面then方法回调的参数
  • 如果回调中返回的是Promise,那后面then方法的回调会等待它的结束

异常处理

then中回调的onRejected方法

// Promise 异常处理

function ajax (url) {
     
  return new Promise(function (resolve, reject) {
     
    // foo()
    // throw new Error()

    var xhr = new XMLHttpRequest()
    xhr.open('GET', url) 
    xhr.responseType = 'json'
    xhr.onload = function () {
     
      if (this.status === 200) {
     
        resolve(this.response)
      } else {
     
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

// onRejected回调为promise执行中的异常做处理
ajax('/api/users1.json').then(
  function onFulfilled (value) {
     
    console.log('onFulfilled', value);
  }, 
  function onRejected (error) {
     
    console.log('onRejected', error);
  }
)

.catch() (推荐)

promise中如果有异常,都会调用reject方法,还可以使用.catch()

使用.catch() 方法更为常见,因为更符合链式调用

ajax('/api/users1.json')
  .then(
    function onFulfilled (value) {
     
      console.log('onFulfilled', value);
    }
  )
  .catch(
    function onRejected (error) {
     
      console.log('onRejected', error);
    }
  )

.catch形式和前面then里面的第二个参数的形式,两者异常捕获的区别:

  • .catch() 是对上一个.then()返回的promise进行处理,不过第一个promise的报错也顺延到了catch中,而then的第二个参数形式,只能捕获第一个promise的报错,如果当前then的resolve函数处理中有报错是捕获不到的。
ajax('/api/users.json').then(
  function onFulfilled (value) {
     
    console.log('onFulfilled', value);
    return ajax('/error-url')
  }, 
  function onRejected (error) {
     
    console.log('onRejected', error);
  }
)

// 每个then方法都会返回一个promise对象,catch实际是为上一个then返回的promise对象指定失败的回调,并不是给第一个指定的,因为promise执行时,报错会传递,所以在这里能捕获到第一个promise的异常
// 如果在then方法中返回了第二个promise,如果在这个promise执行过程中出现了异常,那使用then的第二个参数注册时失败回调,捕获不到第二个promise的异常,因为它只是给第一个promise对象注册了失败回调

ajax('/api/users.json')
  .then(
    function onFulfilled (value) {
     
      console.log('onFulfilled', value);
    }
  )
  .catch(
    function onRejected (error) {
     
      console.log('onRejected', error);
    }
  )

所以.catch()是给整个promise链条注册的一个失败回调。推荐使用!!!

全局对象上的unhandledrejection事件

还可以在全局对象上注册一个unhandledrejection事件,处理那些代码中没有被手动捕获的promise异常,当然并不推荐使用

更合理的是:在代码中明确捕获每一个可能的异常,而不是丢给全局处理。

// 全局捕获 Promise 异常,类似于 window.onerror
window.addEventListener('unhandledrejection', event => {
     
  const {
      reason, promise } = event
  console.log(reason, promise);
  // reason => Promise 失败的原因,一般是一个错误对象
  // promise => 出现异常的 Promise 对象
  event.preventDefault()
}, false)

// node中全局捕获 Promise 异常
process.on('unhandledrejection', (reason, promise) => {
     
  console.log(reason, promise);
  // reason => Promise 失败的原因,一般是一个错误对象
  // promise => 出现异常的 Promise 对象
}, false)

常用 Promise 静态方法

// 常用 Promise 静态方法

Promise.resolve('foo')
  .then(function (value) {
     
    console.log(value);
    // => foo
  })

new Promise(function (resolve, reject) {
     
  resolve('foo')
})

var promise = ajax('/api/users.json')
var promise2 = Promise.resolve(promise)

console.log(promise === promise2);
// => true

Promise.resolve({
     
  then: function (onFulfilled, onRejected) {
     
    onFulfilled('foo')
  }
})
.then(function (value) {
     
  console.log(value);
  // => foo
})

Promise.reject(new Error('rejected'))
  .catch(function (error) {
     
    console.log(error);
  })
  
Promise.reject('anything')
  .catch(function (error) {
     
    console.log(error);
  })

Promise并行执行

简单的并行执行,未判断执行是否完成

ajax('/api/users.json')
ajax('/api/posts.json')

Promise.all

Promise.all()等待所有任务的结束

传统使用计数器,每次执行结束+1,当计数器的数量等于任务数时,所有任务执行结束。这种方法比较麻烦,而且还需要考虑出现异常的情况

  1. promise.all方法,将多个promise合并成一个promise统一管理。
  2. Promise.all接收一个数组,数组中每个元素都是都是一个Promise对象,可以看作是一个个的异步任务
  3. 这个方法返回一个全新的promise对象,当内部所有的promise完成后,这个返回的全新的promise才会完成
  4. 此时这个promise对象拿到的结果是一个数组,数组中每个元素都是对应任务的执行结果
  5. 只有所有任务都成,结果才会成功,只要有一个失败,那这个结果就是失败的
var promise = Promise.all([
ajax('/api/users.json'),
ajax('/api/posts.json')
])

promise.then(function (value) {
     
  console.log(value);
})
ajax('/api/urls.json')
  .then(value => {
     
    const urls = Object.values(value)
    const tasks = urls.map(url => ajax(url))

    return Promise.all(tasks)
  }).then (values => {
     
    console.log(values);
  })

Promise.race

Promise.race()只会等待第一个任务的结束

const request = ajax('/api/posts.json')
const timeout = new Promise((resolve, reject) => {
     
  setTimeout(() => reject(new Error('timeout')), 500)
})
Promise.race([
  request,
  timeout
]).then(values => {
     
  console.log(values);
}).catch(error => {
     
  console.log(error);
})

Promise执行时序

宏任务 VS 微任务

即便Promise中没有任何的异步操作,它的回调函数也仍然会进入到回调队列中排队,也就是说必须要等待当前所有的同步代码执行结束后才会执行Promise中的回调,但是这种说法并不严谨。

// 微任务
console.log('global start');
Promise.resolve()
  .then(() => {
     
    console.log('promise');
  })
console.log('global end');

// => global start
// => global end
// => global promise
// 微任务
console.log('global start');
Promise.resolve()
  .then(() => {
     
    console.log('promise');
  })
  .then(() => {
     
    console.log('promise 2');
  })
  .then(() => {
     
    console.log('promise 3');
  })
console.log('global end');

// => global start
// => global end
// => global promise
// => global promise 2
// => global promise 3
// 微任务
console.log('global start');
setTimeout(() => {
     
  console.log('setTimeout');
}, 0)
Promise.resolve()
  .then(() => {
     
    console.log('promise');
  })
  .then(() => {
     
    console.log('promise 2');
  })
  .then(() => {
     
    console.log('promise 3');
  })
console.log('global end');

// => global start
// => global end
// => global promise
// => global promise 2
// => global promise 3
// => global setTimeout
  1. 回调队列中的任务称为宏任务,宏任务执行过程中可以临时加上一些额外的需求,对于这些额外的需求,可以选择作为一个新的宏任务进到队列中排队,也可以作为当前任务的“微任务”,直接在当前任务结束过后立即执行
  2. Promise的回调会作为微任务执行,会在本轮调用结束的末尾自动执行
  3. setTimeout会以宏任务的形式进到队列的末尾
  4. 所以会先打印promise,再打印setTimeout

微任务是为了提高应用整体的响应能力,目前绝大多数异步调用都是作为宏任务执行
Promise && MutationObserver 和 node 中的 process.nextTick会作为微任务执行在本轮调用结束的末尾自动执行

Generator异步方案

Promise vs. Callback

// Callback hell
ajax('/api/url1', (error, value) => {
     
  ajax('/api/url2', (error, value) => {
     
    ajax('/api/url3', (error, value) => {
     
      ajax('/api/url4', (error, value) => {
     

      })
    })
  })
})
// Promise chain
ajax('/api/users.json')
  .then(function (value) {
     
    console.log(value);
    return ajax('/api/users.json')
  })
  .then(function (value) {
     
    console.log(value);
    return 'foo'
  })
  .then(function (value) {
     
    console.log(value);
  })
  .then(function (value) {
     
    console.log(value);
  })

Promise的链式调用依然会有很多的回调函数,虽然它们之前没有相互嵌套,但是还是没有传统的同步代码的可读性好

如果有类似下面这种代码,就比较简洁易读了

// sync mode code
try {
     
  const value1 = ajax('/api/url1')
  console.log(value1);
  const value2 = ajax('/api/url2')
  console.log(value2);
  const value3 = ajax('/api/url3')
  console.log(value3);
  const value4 = ajax('/api/url4')
  console.log(value4);
} catch (e) {
     
  console.log(e);
}

Generator

ES2015提供的Generator(生成器函数)

生成器函数回顾

相比于普通函数,Generator函数多了一个 *

function* foo () {
     
  console.log('foo');
  try {
     
    const res = yield 'foo'
    console.log(res);
  } catch (e) {
     
    console.log(e);
  }
}

const generator = foo()
const result = generator.next()
console.log(result);

// generator.next('bar')
generator.throw(new Error('Generator error'))
  1. 调用Generator函数并不会立即执行这个函数,而是得到一个Generator对象,只有手动调用.next方法,这个函数的函数体才会开始执行
  2. 在函数内部可以随时使用yield关键词向外返回一个值,在next方法返回对象当中拿到这个值
  3. 在返回的对象中还有done属性,表示这个Generator是否已经全部执行结束
  4. yield关键词并不会像return一样立即结束这个函数执行,它只是暂停执行,直到外界下一次调用Generator对象的.next方法时,会继续从yield这个位置往下执行
  5. 另外调用Generator对象的.next方法时,传入了参数,传入的参数会作为yield语句的返回值,也就是说在yield的左边是可以接收到这个值的,
  6. 除此之外,在外部手动调用Generator对象的throw方法,这个方法可以对Generator内部抛出异常,通过try…catch捕获这个异常
Generator 配合 Promise 异步方案
function ajax (url) {
     
  return new Promise(function (resolve, reject) {
     
    // foo()
    // throw new Error()

    var xhr = new XMLHttpRequest()
    xhr.open('GET', url) 
    xhr.responseType = 'json'
    xhr.onload = function () {
     
      if (this.status === 200) {
     
        resolve(this.response)
      } else {
     
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

function* main () {
     
  const users = yield ajax('/api/users.json')
  console.log(users);
}
const g = main()
const result = g.next()
console.log(result);
result.value.then(data => {
     
  g.next(data)
})
function* main () {
     
  const users = yield ajax('/api/users.json')
  console.log(users);

  const posts = yield ajax('/api/posts.json')
  console.log(posts);
}
const g = main()
const result = g.next()
console.log(result);
result.value.then(data => {
     
  const result2 = g.next(data)
  result2.value.then(data => {
     
    g.next(data)
  })
})
function* main () {
     
  const users = yield ajax('/api/users.json')
  console.log(users);

  const posts = yield ajax('/api/posts.json')
  console.log(posts);
}
const g = main()
const result = g.next()

result.value.then(data => {
     
  const result2 = g.next(data)
  if (result2.done) return
  result2.value.then(data => {
     
    const result3 = g.next(data)
    if (result3.done) return
    result3.value.then(data => {
     
      g.next(data)
    })
  })
})

这里可以使用递归判断是否执行结束

function* main () {
     
  try {
     
    const users = yield ajax('/api/users.json')
    console.log(users);
  
    const posts = yield ajax('/api/posts.json')
    console.log(posts);
  
    const url = yield ajax('/api/url1.json')
    console.log(url);
  } catch (e) {
     
    console.log(e);
  }
}
const g = main()

// 递归执行
function handleResult (result) {
     
  if (result.done) return // 生成器函数结束
  result.value.then(data => {
     
    handleResult(g.next(data))
  }, error => {
     
    g.throw(error)
  })
}
handleResult(g.next())

可以封装使用

function co (generator) {
     
  const g = generator()
  function handleResult (result) {
     
    if (result.done) return // 生成器函数结束
    result.value.then(data => {
     
      handleResult(g.next(data))
    }, error => {
     
      g.throw(error)
    })
  }
  handleResult(g.next())
}

co(main)

Async函数

Async / Await 语法糖

语言层面的异步编程标准

async function main () {
     
  try {
     
    const users = await ajax('/api/users.json')
    console.log(users);
  
    const posts = await ajax('/api/posts.json')
    console.log(posts);
  
    const url = await ajax('/api/url1.json')
    console.log(url);
  } catch (e) {
     
    console.log(e);
  }
}

const promise = main()

promise.then(() => {
     
  console.log('all completed');
})
  • 使用generator函数还需要手动编写一个执行器函数,例如co,比较麻烦,在ECMAScript2017中新增了一个Async的函数,提供了扁平化的异步编程体验,是语言层面标准的异步编程语法,使用起来更方便
  • 其实Async函数就是生成器函数的一种语法糖,语法上很类似,Async函数在普通函数前加一个 async, 将内部的 yield 关键词换成了 await
  • 可以在外部直接调用,不需要再配合执行器使用
  • Async函数可以返回一个promise对象,更加利于我们对整体代码进行控制
    await关键字只能出现在async内部,不能在外部直接使用

手写Promise源码

一、Promise核心逻辑实现

首先分析其原理

  1. promise就是一个类

    在执行类的时候需要传递一个执行器进去,执行器会立即执行
  2. Promise中有三种状态,分别为成功-fulfilled 失败-rejected 等待pending

    pending -> fulfilled

    pending -> rejected

    一旦状态确定就不可更改
  3. resolve和reject函数是用来更改状态的

    resolve:fulfilled

    reject:rejected
  4. then方法内部做的事情就是判断状态
    如果状态是成功,调用成功回调函数
    如果状态是失败,调用失败回调函数
    then方法是被定义在原型对象中的
  5. then成功回调有一个参数,表示成功之后的值;
    then失败回调有一个参数,表示失败后的原因
  6. 同一个promise对象下面的then方法是可以被多次调用的
  7. then方法是可以被链式调用的,后面then方法的回调函数拿到的值是上一个then方法的回调函数的返回值
// 定义成常量是为了复用且代码有提示,变量没有提示
const PENDING = 'pending'  // 等待
const FULFILLED = 'fulfilled'  // 成功
const REJECTED = 'rejected'  // 失败

// 定义一个构造函数
class MyPromise {
     
  constructor (exector) {
     
    // exector是一个执行器,进入会立即执行,并传入resolve和reject方法
    exector(this.resolve, this.reject)
  }

  // promise状态 实例对象的一个属性,初始为等待
  status = PENDING
  // 成功之后的值
  value = undefined
  // 失败之后的原因
  reason = undefined

  // 定义成箭头函数指向当前类的实例化的对象
  resolve = value => {
     
    // 判断状态是不是等待,如果不是等待(pending)则阻止程序向下执行
    if (this.status !== PENDING) return
    // 将状态更改为成功
    this.status = FULFILLED
    // 保存成功之后的值
    this.value = value
  }

  reject = reason => {
     
    if (this.status !== PENDING) return
    // 将状态更改为失败
    this.status = REJECTED
    // 保存失败之后的原因
    this.reason = reason
  }

  then (successCallback, failCallback) {
     
    // 先判断状态
    if (this.status === FULFILLED) {
     
      // 调用成功回调,并把值返回
      successCallback(this.value)
    } else if (this.status === REJECTED) {
     
      // 调用失败回调,并把原因返回
      failCallback(this.reason)
    }
  }
}

module.exports = MyPromise
const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  resolve('成功')
  // reject('失败')
})

promise.then(value => {
     
  console.log('value', value);
}, reason => {
     
  console.log('reason', reason);
})

二、在Promise类中加入异步逻辑

上面是没有经过异步处理的,如果有异步逻辑加进来,会有一些问题

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
     
  constructor (exector) {
     
    exector(this.resolve, this.reject)
  }

  status = PENDING
  value = undefined
  reason = undefined
  // 成功回调
  successCallback = undefined
  // 失败回调
  failCallback = undefined

  resolve = value => {
     
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    // 判断成功回调是否存在,如果存在就调用
    this.successCallback && this.successCallback(this.value)
  }

  reject = reason => {
     
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    // 判断失败回调是否存在,如果存在就调用
    this.failCallback && this.failCallback(this.reason)
  }

  then (successCallback, failCallback) {
     
    if (this.status === FULFILLED) {
     
      successCallback(this.value)
    } else if (this.status === REJECTED) {
     
      failCallback(this.reason)
    } else {
     
      // 等待
      // 因为不知道状态,所以将成功回调和失败回调存储起来
      // 等待执行成功或失败函数的时候再传递
      this.successCallback = successCallback
      this.failCallback = failCallback
    }
  }
}

module.exports = MyPromise
const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  // 主线程代码立即执行,setTimeout是异步代码,then会马上执行
  // 这个时候判断promise的状态,状态是pending,但是之前并没有判断这个状态,即没有处理异步情况
  setTimeout(() => {
     
    resolve('成功')
  }, 2000)
})

promise.then(value => {
     
  console.log('value', value);
}, reason => {
     
  console.log('reason', reason);
})

三、实现then方法多次调用添加多个处理函数

promise的then方法是可以被多次调用的。

这里如果有三个then的调用,分两种情况:

  1. 如果是同步回调,那么直接返回当前的值就行;
  2. 如果是异步回调,那么保存的成功失败的回调,需要用不同的值保存,因为都互不相同,之前的代码需要改进

保存到数组中,最后统一执行

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
     
  constructor (exector) {
     
    exector(this.resolve, this.reject)
  }

  status = PENDING
  value = undefined
  reason = undefined
  // 定义一个成功回调参数,初始化一个空数组
  successCallback = []
  // 义一个失败回调参数,初始化一个空数组
  failCallback = []

  resolve = value => {
     
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    // 判断成功回调是否存在,如果存在就调用
    // 循环回调数组,把数组前面的方法弹出来并且直接调用
    // shift方法是在数组中删除值,每执行一个就删除一个,最终变为0
    while (this.successCallback.length) this.successCallback.shift()(this.value)
  }

  reject = reason => {
     
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    // 判断失败回调是否存在,如果存在就调用
    // 循环回调数组,把数组前面的方法弹出来并且直接调用
    while (this.failCallback.length) this.failCallback.shift()(this.reason)
  }

  then (successCallback, failCallback) {
     
    if (this.status === FULFILLED) {
     
      successCallback(this.value)
    } else if (this.status === REJECTED) {
     
      failCallback(this.reason)
    } else {
     
      // 等待
      // 将成功回调和失败回调都保存在数组中
      this.successCallback.push(successCallback)
      this.failCallback.push(failCallback)
    }
  }
}

module.exports = MyPromise
const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  setTimeout(() => {
     
    resolve('成功')
  }, 2000)
})

promise.then(value => {
     
  console.log('value', value);
}, reason => {
     
  console.log('reason', reason);
})

promise.then(value => {
     
  console.log('value', value);
}, reason => {
     
  console.log('reason', reason);
})

promise.then(value => {
     
  console.log('value', value);
}, reason => {
     
  console.log('reason', reason);
})

实现then方法的链式调用

then方法要链式调用那么就需要返回一个promise对象,

then方法的return返回值作为下一个then方法的参数

then方法还能return一个promise对象,那么如果是一个promise对象,那么就需要判断它的状态

  • 上一个 promise 回调函数的 return 返回值是一个普通值
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
     
  constructor (exector) {
     
    exector(this.resolve, this.reject)
  }

  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []

  resolve = value => {
     
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    while (this.successCallback.length) this.successCallback.shift()(this.value)
  }

  reject = reason => {
     
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    while (this.failCallback.length) this.failCallback.shift()(this.reason)
  }

  then (successCallback, failCallback) {
     
    // this.then方法返回第一个promise对象
    let promise2 = new MyPromise((resolve, reject) => {
     
      if (this.status === FULFILLED) {
     
        // x 是上一个 promise 回调函数的 return 返回值
        // x 的值是普通值,直接调用 resolve
        let x = successCallback(this.value)
        resolve(x)
      } else if (this.status === REJECTED) {
     
        failCallback(this.reason)
      } else {
     
        this.successCallback.push(successCallback)
        this.failCallback.push(failCallback)
      }
    });
    return promise2
  }
}

module.exports = MyPromise
const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  // 目前这里只处理同步的问题
  resolve('成功')
})

promise.then(value => {
     
  console.log('value1', value);
  return 100
}).then(value => {
     
  console.log('value2', value);
})
  • 上一个 promise 回调函数的 return 返回值是一个promise对象
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
     
  constructor (exector) {
     
    exector(this.resolve, this.reject)
  }

  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []

  resolve = value => {
     
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    while (this.successCallback.length) this.successCallback.shift()(this.value)
  }

  reject = reason => {
     
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    while (this.failCallback.length) this.failCallback.shift()(this.reason)
  }

  then (successCallback, failCallback) {
     
    // this.then方法返回第一个promise对象
    let promise2 = new MyPromise((resolve, reject) => {
     
      if (this.status === FULFILLED) {
     
        // x 是上一个 promise 回调函数的 return 返回值
        // 判断 x 的值是普通值还是 promise 对象
        // 如果是普通值,直接调用 resolve
        // 如果是 promise 对象,查看 promise 对象返回的结果
        // 再根据 promise 对象返回的结果,决定调用 resolve 还是 reject
        let x = successCallback(this.value)
        resolvePromise(x, resolve, reject)
      } else if (this.status === REJECTED) {
     
        failCallback(this.reason)
      } else {
     
        // 等待
        // 将成功回调和失败回调都保存在数组中
        this.successCallback.push(successCallback)
        this.failCallback.push(failCallback)
      }
    });
    return promise2
  }
}

function resolvePromise(x, resolve, reject) {
     
  // 判断 x 是不是其实例对象
  if (x instanceof MyPromise) {
     
    // promise 对象
    // x.then(value => resolve(value), reason => reject(reason))
    x.then(resolve, reject)
  } else {
     
    // 普通值
    resolve(x)
  }
}

module.exports = MyPromise
const MyPromise = require('./myPromise.js')
const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  resolve('成功')
})

function other () {
     
  return new MyPromise((resolve, reject) => {
     
    resolve('other')
  })
}

promise.then(value => {
     
  console.log(1);
  console.log('value1', value);
  // return 100
  return other()
}).then(value => {
     
  console.log(2);
  console.log('value2', value);
})

五、then方法链式调用识别Promise对象自返回

如果then方法返回的是自己的promise对象,则会发生promise的嵌套,这个时候程序会报错

var promise = new Promise(function (resolve, reject) {
     
  resolve(100)
})

var p1 = promise.then(function(value) {
     
  console.log(value);
  return p1
})
// => 100
// => Uncaught (in promise) TypeError: Chaining cycle detected for promise #

var promise = new Promise(function (resolve, reject) {
     
  resolve(100)
})

var p1 = promise.then(function(value) {
     
  console.log(value);
  return p1
})

p1.then(function () {
     }, function (reason) {
     
  console.log(reason.message);
})
// => 100
// => Chaining cycle detected for promise #

所以为了避免这种情况,我们需要改造一下then方法

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
     
  constructor (exector) {
     
    exector(this.resolve, this.reject)
  }

  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []

  resolve = value => {
     
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    while (this.successCallback.length) this.successCallback.shift()(this.value)
  }

  reject = reason => {
     
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    while (this.failCallback.length) this.failCallback.shift()(this.reason)
  }

  then (successCallback, failCallback) {
     
    let promise2 = new MyPromise((resolve, reject) => {
     
      if (this.status === FULFILLED) {
     
        // 因为 new Promise 需要执行完成之后才有 promise2 ,同步代码中没有 promise2 ,
        // 所以这部分代码需要异步执行
        // 使用setTimeout不是为了延迟,而是为了变成异步代码,所以时间写为0
        setTimeout(() => {
     
          let x = successCallback(this.value)
          // 需要判断then之后的如return的peomise对象和原来的是不是一样的,
          // 判断 x 和 promise2 是否相等,所以 resolvePromise 给中传递 promise2 过去
          resolvePromise(promise2, x, resolve, reject)
        }, 0)
      } else if (this.status === REJECTED) {
     
        failCallback(this.reason)
      } else {
     
        this.successCallback.push(successCallback)
        this.failCallback.push(failCallback)
      }
    });
    return promise2
  }
}

function resolvePromise(promise2, x, resolve, reject) {
     
  // 如果相等,说明return的是自己,抛出类型错误并返回
  if (promise2 === x) {
     
    return reject(new TypeError('Chaining cycle detected for promise #'))
  }
  if (x instanceof MyPromise) {
     
    x.then(resolve, reject)
  } else {
     
    resolve(x)
  }
}

module.exports = MyPromise
const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  resolve(100)
})


let p1 = promise.then(value => {
     
  console.log(value);
  return p1
})

p1.then(function () {
     }, function (reason) {
     
  console.log(reason.message);
})

// => 100
// => Chaining cycle detected for promise #

六、捕获错误及then链式调用其他状态代码补充

目前我们在Promise类中没有进行任何处理,所以我们需要捕获和处理错误。

1.捕获执行器的错误

捕获执行器中的代码,如果执行器中有代码错误,那么promise的状态要弄成错误状态

constructor (exector) {
     
    // 捕获错误,如果有错误就执行reject
    try {
     
      exector(this.resolve, this.reject)
    } catch (e) {
     
      this.reject(e)
    }
  }
const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  throw new Error('executor error')
  resolve(100)
})

promise.then(value => {
     
  console.log('value', value);
}, reason => {
     
  console.log('reason', reason);
})

2.执行的时候报错捕获

捕获执行then时报错的信息

then (successCallback, failCallback) {
     
    let promise2 = new MyPromise((resolve, reject) => {
     
      if (this.status === FULFILLED) {
     
        setTimeout(() => {
     
          // 如果回调中报错的话就执行reject
          try {
     
            let x = successCallback(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
     
            reject(e)
          }
        }, 0)
      } else if (this.status === REJECTED) {
     
        failCallback(this.reason)
      } else {
     
        this.successCallback.push(successCallback)
        this.failCallback.push(failCallback)
      }
    });
    return promise2
  }
const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  resolve(100)
})

promise.then(value => {
     
  console.log('value', value);
  throw new Error('then error')
}, reason => {
     
  console.log('reason', reason);
}).then(value => {
     
  console.log('value', value);
}, reason => {
     
  console.log('reason', reason);
})

3.错误之后的链式调用

then (successCallback, failCallback) {
     
    let promise2 = new MyPromise((resolve, reject) => {
     
      if (this.status === FULFILLED) {
     
        setTimeout(() => {
     
          // 如果回调中报错的话就执行reject
          try {
     
            let x = successCallback(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
     
            reject(e)
          }
        }, 0)
      } else if (this.status === REJECTED) {
     
        // 在状态是reject的时候对返回的promise进行处理
        setTimeout(() => {
     
          // 如果回调中报错的话就执行reject
          try {
     
            let x = failCallback(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
     
            reject(e)
          }
        }, 0)
      } else {
     
        this.successCallback.push(successCallback)
        this.failCallback.push(failCallback)
      }
    });
    return promise2
  }
const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  // throw new Error('executor error')
  // resolve(100)
  reject('失败')
})

promise.then(value => {
     
  console.log('value', value);
  // throw new Error('then error')
}, reason => {
     
  console.log('reason', reason.message);
  return 10000
}).then(value => {
     
  console.log(value);
})

4.异步状态下链式调用

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
     
  constructor (exector) {
     
    // 捕获错误,如果有错误就执行reject
    try {
     
      exector(this.resolve, this.reject)
    } catch (e) {
     
      this.reject(e)
    }
  }

  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []

  resolve = value => {
     
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    while (this.successCallback.length) this.successCallback.shift()()
  }

  reject = reason => {
     
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    while (this.failCallback.length) this.failCallback.shift()()
  }

  then (successCallback, failCallback) {
     
    let promise2 = new MyPromise((resolve, reject) => {
     
      if (this.status === FULFILLED) {
     
        setTimeout(() => {
     
          // 如果回调中报错的话就执行reject
          try {
     
            let x = successCallback(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
     
            reject(e)
          }
        }, 0)
      } else if (this.status === REJECTED) {
     
        // 在状态是reject的时候对返回的promise进行处理
        setTimeout(() => {
     
          // 如果回调中报错的话就执行reject
          try {
     
            let x = failCallback(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
     
            reject(e)
          }
        }, 0)
      } else {
     
        this.successCallback.push(() => {
     
          setTimeout(() => {
     
            // 如果回调中报错的话就执行reject
            try {
     
              let x = successCallback(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
     
              reject(e)
            }
          }, 0)
        })
        this.failCallback.push(() => {
     
          setTimeout(() => {
     
            // 如果回调中报错的话就执行reject
            try {
     
              let x = failCallback(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
     
              reject(e)
            }
          }, 0)
        })
      }
    });
    return promise2
  }
}

function resolvePromise(promise2, x, resolve, reject) {
     
  // 如果相等,说明return的是自己,抛出类型错误并返回
  if (promise2 === x) {
     
    return reject(new TypeError('Chaining cycle detected for promise #'))
  }
  if (x instanceof MyPromise) {
     
    x.then(resolve, reject)
  } else {
     
    resolve(x)
  }
}

module.exports = MyPromise
const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  setTimeout(() => {
     
    resolve('成功......')
  }, 2000)
})

promise.then(value => {
     
  console.log('value1', value);
  return '123'
}, reason => {
     
  console.log('reason1', reason.message);
  return 10000
}).then(value => {
     
  console.log('value2', value);
}, reason => {
     
  console.log('reason2', reason.message);
})

// => value1 成功......
// => value2 123

七、将then方法的参数变成可选参数

then方法的两个参数都是可选参数,我们可以不传参数。

下面的参数可以传递到最后进行返回

var promise = new Promise(function (resolve, reject) {
     
  resolve(100)
})

promise
  .then()
  .then()
  .then(value => console.log(value))
// => 100

// 相当于
promise
  .then(value => value)
  .then(value => value)
  .then(value => console.log(value))

所以我们修改一下then方法

then (successCallback, failCallback) {
     
    // 这里进行判断,如果有回调就选择回调,如果没有回调就传一个函数,把参数传递下去
    successCallback = successCallback ? successCallback : value => value
    // 错误函数也是进行赋值,把错误信息抛出
    failCallback = failCallback ? failCallback : reason => reason
    let promise2 = new MyPromise((resolve, reject) => {
     
      if (this.status === FULFILLED) {
     
        setTimeout(() => {
     
          try {
     
            let x = successCallback(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
     
            reject(e)
          }
        }, 0)
      } else if (this.status === REJECTED) {
     
        setTimeout(() => {
     
          try {
     
            let x = failCallback(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
     
            reject(e)
          }
        }, 0)
      } else {
     
        this.successCallback.push(() => {
     
          setTimeout(() => {
     
            try {
     
              let x = successCallback(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
     
              reject(e)
            }
          }, 0)
        })
        this.failCallback.push(() => {
     
          setTimeout(() => {
     
            try {
     
              let x = failCallback(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
     
              reject(e)
            }
          }, 0)
        })
      }
    });
    return promise2
  }

resolve之后

onst myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  resolve('成功')
})

promise
  .then()
  .then()
  .then(value => console.log(value))

// => 成功

reject之后

const myPromise = require('./myPromise.js')

let promise = new myPromise((resolve, reject)=> {
     
  reject('失败')
})

promise
  .then()
  .then()
  .then(value => console.log(value), reason => console.log(reason))

// => 失败

八、promise.all方法的实现

promise.all方法是解决异步并发问题的

// 如果p1是两秒之后执行的,p2是立即执行的,那么根据正常的是p2在p1的前面
// 如果我们在all中指定了执行顺序,那么会根据我们传递的顺序进行执行
function p1() {
     
  return new Promise((resolve, reject) => {
     
    setTimeout(function() {
     
      resolve('p1')
    }, 2000)
  })
}
function p2() {
     
  return new Promise((resolve, reject) => {
     
    resolve('p2')
  })
}

Promise.all(['a', 'b', p1(), p2(), 'c']).then(function(result) {
     
  console.log(result);
  // => ["a", "b", "p1", "p2", "c"]
})

分析一下:

  • all方法接收一个数组,数组中可以是普通值也可以是promise对象
  • 数组中值的顺序一定是我们得到的结果的顺序
  • promise返回值也是一个promise对象,可以调用then方法
  • 如果数组中所有值是成功的,那么then里面就是成功回调,如果有一个值是失败的,那么then里面就是失败的
  • 使用all方法是用类直接调用,那么all一定是一个静态方法
// promise.all是一个静态方法,所以需要加static声明为静态方法
  static all (array) {
     
    // 结果数组
    let result = []
    // 计数器
    let index = 0
    return new MyPromise((resolve, reject) => {
     
      let addData = (key, value) => {
     
        result[key] = value
        index ++
        // 如果计数器和数组的长度相同,说明所有的元素都已执行完毕,可以输出了
        if (index === array.length) {
     
          resolve(result)
        }
      }
      for (let i = 0; i< array.length; i++) {
     
        let currrent = array[i]
        if (currrent instanceof MyPromise) {
     
          // promise对象就执行then,如果是resolve就把值添加到数组中去,如果是错误就执行reject返回
          currrent.then(value => addData(i, value), reason => reject(reason))
        } else {
     
          // 普通值就添加到对应的数组中去
          addData(i, array[i])
        }
      }
    })
  }
const MyPromise = require('./myPromise.js')

function p1() {
     
  return new MyPromise((resolve, reject) => {
     
    setTimeout(function() {
     
      resolve('p1')
    }, 2000)
  })
}
function p2() {
     
  return new MyPromise((resolve, reject) => {
     
    resolve('p2')
  })
}

MyPromise.all(['a', 'b', p1(), p2(), 'c']).then(function(result) {
     
  console.log(result);
  // => ["a", "b", "p1", "p2", "c"]
})

九、Promise.resolve方法的实现

  • 如果参数就是一个promise对象,直接返回,如果是一个值,那么需要生成一个promise对象,把值进行返回
  • 是Promise类的一个静态方法
function p1() {
     
  return new Promise((resolve, reject) => {
     
    setTimeout(function() {
     
      resolve('p1')
    }, 2000)
  })
}
function p2() {
     
  return new Promise((resolve, reject) => {
     
    resolve('p2')
  })
}

Promise.resolve(100).then(value => console.log(value))
// => 100
Promise.resolve(p1()).then(value => console.log(value))
// => p1
static resolve (value) {
     
    // 如果是promise对象,就直接返回
    if (value instanceof MyPromise) return value
    // 如果是普通值,就返回一个promise对象
    return new Promise(resolve => resolve(value)) 
  }
const MyPromise = require('./myPromise.js')

function p1() {
     
  return new MyPromise((resolve, reject) => {
     
    setTimeout(function() {
     
      resolve('p1')
    }, 2000)
  })
}
function p2() {
     
  return new MyPromise((resolve, reject) => {
     
    resolve('p2')
  })
}

MyPromise.resolve(100).then(value => console.log(value))
// => 100
MyPromise.resolve(p1()).then(value => console.log(value))
// => p1

十、finally方法的实现

  • 无论当前最终状态时成功还是失败,finally都会执行
  • 我们可以在finally方法之后调用then方法拿到结果
  • finally不是一个而静态方法,这个函数是在原型对象上用的,所以需要定义在原型对象上
function p1() {
     
      return new Promise((resolve, reject) => {
     
        resolve('hello')
      })
    }

    p1().finally(() => {
     
      console.log('finally');
    }).then(value => console.log(value))
    // => finally
    // => hello
finally (callback) {
     
    // 拿到当前的promise的状态,可以使用then方法,而且不管怎样都返回callback
    // 而且then方法就是返回一个promise对象,那么我们直接返回then方法调用之后的结果即可
    // 我们需要在回调之后拿到成功的回调,所以需要把value也return出去
    // 失败的回调也要抛出原因
    // 如果callback是一个异步的promise对象,还需要等待其执行完毕,所以需要静态方法resolve
    return this.then(value => {
     
      // bacallback调用之后返回的promise传递出去,并且执行promise,且在成功之后返回value
      return MyPromise.resolve(callback()).then(() => value)
    }, reason => {
     
      // 失败之后调用then方法,然后把失败的原因返回出去
      return MyPromise.resolve(callback()).then(() => {
     throw reason})
    })
  }
const MyPromise = require('./myPromise.js')

function p1() {
     
  return new MyPromise((resolve, reject) => {
     
    // reject('hello')
    resolve('hello')
  })
}

function p2() {
     
  return new MyPromise((resolve, reject) => {
     
    setTimeout(function() {
     
      resolve('p2')
    }, 2000)
  })
}

p1().finally(() => {
     
  console.log('finally');
  return p2()
}).then(value => console.log(value), reason => console.log(reason))
// => finally
// => hello

十一、catch方法的实现

  • catch方法是为了捕获promise对象的所有错误回调的
  • 直接调用then方法,然后成功的地方传递undefined,错误的地方传递reason
  • catch方法是作用在原型对象上的方法
catch (failCallback) {
     
    return this.then(undefined, failCallback)
  }
const MyPromise = require('./myPromise.js')

function p1() {
     
  return new MyPromise((resolve, reject) => {
     
    reject('reject')
    // resolve('hello')
  })
}

p1()
  .then(value => console.log(value))
  .catch(reason => console.log(reason))
// => reject

你可能感兴趣的:(大前端学习笔记,javascript,es6,html5)