promise - Generator - async

promise是什么?

1、主要用于异步计算
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
3、可以在对象之间传递和操作promise,帮助我们处理队列

// 基本使用

const promise = new Promise(function(resolve,reject){
    //实现'承诺'
    // resolve(100) //成功
    reject(new Error('promise rejected')) //失败,返回错误信息
})

promise.then(function(value){
   console.log('成功回调,获100',value )
},function(err){
    console.log('失败',err)
})

// promise 封装ajax

// Promise 方式的 AJAX

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/foo.json').then(function (res) {
  console.log(res)
}, function (error) {
  console.log(error)
})
// 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()
  })
}

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

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

// console.log(promise2 === promise)

ajax('/api/users.json')
  .then(function (value) {
    console.log(1111)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(2222)
    console.log(value)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(3333)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(4444)
    return 'foo'
  }) // => Promise
  .then(function (value) {
    console.log(5555)
    console.log(value)
  })

可使用promise的then方法实现异步的扁平化,实现链式调用

  • promise的then方法会从新返回一个全新的Promise对象
  • 后面的then方法就是在为上一个then方法返回的promise注册回调
  • 前面的then方法中回调函数的返回值作为后面then方法回调的参数
  • 如果回调返回的是一个Promise,那么后面then方法的回调会等待他的结束

Promise.resolve()

// 相当于直接返回新的promise对象
 Promise.resolve('f00').then((res)=>{
     console.log(res) //value  
 })

 //相当于

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

 var promise1 = ajax('api/user.json')
 var promise2 = Promise.resolve(promise1)
 console.log(promise1 ===promise2)//true
 
 Promise.reject('anything')
.catch(function(err){
    console.log(err)
})

Promise 并行执行 all()
将多个组合成一个新的Promise,等待所有任务结束之后,统一管理,接收数组形式

  
// Promise all
var promiseall= Promise.all([
    ajax('api/user.json'),
    ajax('api/user.json'),
    ajax('api/user.json'),
])

// 全部完成之后,新的promise才会完成,全部成功才会成功,一个失败执行失败

promiseall.then((res)=>{
    console.log(res)
}).catch((err)=>console.log(err))

Promise.race()

// Promise.race() 等待第一个任务结束之后

const request =  ajax('api/user.json')
const timeout = new Promise((resolve,reject)=>{
    setTimeout(() => reject(new Error('timeout')),3000)
})

Promise.race([
    Request,timeout
]).then(value=>console.log(value))
.catch(err=>console.log(err))

promise读取文件

// promise.js
const fs = require("fs");
const read = function(fileName){
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName,(err,data)=>{
            if (err) {
                reject(err);
            } else{
                resolve(data);
            }
        });
    });
};
read('1.txt').then(res=>{
    console.log(res.toString());
    return read('2.txt');  // 返回新的数据,然后输出
}).then(res => {
    console.log(res.toString());
    return read('3.txt');
}).then(res => {
    console.log(res.toString());
});

Generator

Generator以一种看似顺序、同步的方式实现了异步控制流程,增强了代码可读性
Generator(生成器)是一类特殊的函数,跟普通函数声明时的区别是加了一个*号

// Generator 生成器函数

function * foo(){
    console.log('start')

    yield 'foo'

    console.log(res) // bar
    try{
        const res = yield 'foo';
        console.log(res)
    } catch(e){
        console.log(e)
    }
}
const generator = foo()
// 直到调用next方法才会执行,可通过声明变量方式拿到yield的值

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

//next 只是暂停执行,下一个next会继续向下执行
// 如果传递参数,可以在函数内部接收到
generator.next('bar')

// generator.throw 会向生成器内部抛出异常,可在内部捕获到
generator.throw(new Error('generator Error'))
// Generator 配合 Promise 的异步方案

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

function * main(){
   try{
        const user = yield ajax('api/suer.json')
        console.log(user)
        const post = yield ajax('api/suer.json')
        console.log(post)
    } catch(e){
        console.log(e)
    }
}

const g = main()
const result = g.next()

result.value.then(data=>{
    // g.next(data) //获取到user的值
    const result2 = g.next(data)
    result2.value.then(data=>{
        g.next(data) 
    })
})

//递归执行 生成器函数

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

co(main)

或者 处理请求

function getCallSettings() {
    // utils.ajax方法支持返回promise对象,把得到的promise return出去
    return utils.ajax({
        url: '/dialer/dialerSetting',
        method: "GET",
    });
}
function *dealData() {
    try {
        let settingInfo = yield getCallSettings();
        // do something……
    }
    catch(err) {
        console.log(err); // 接收错误
    }
}

let it = dealData();
let promise = it.next().value; // 注意,这里拿到yield出来的promise
promise.then(
    (info) => {
        it.next(info); // 拿到info传给yield表达式
    }, 
    (err) => {
        it.throw(err); // 抛出错误
    }
);


多请求

function *dealData() {
    let r1 = yield utils.ajax(reqUrl1); // 请求1获取到 r1
    let r2 = yield utils.ajax(reqUrl2); // 请求2获取到 r2

    let reqUrl3 = getUrl(reqUrl1, reqUrl2); // 请求3需要的url依赖于前面两个请求
    let r3 = yield utils.ajax(reqUrl3);
    // do something……
}

生成器执行时会先发出请求1,请求1返回后才会发出请求2,请求2返回之后,再发出请求3。其实在这里请求12之间不存在依赖关系,是可以同时进行的。

async函数

  • async函数是使用async关键字声明的函数
  • async函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。
  • async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。
function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result);
  // expected output: "resolved"
}

asyncCall();

// calling
// resolved
  • async函数的函数体可以被看作是由0个或者多个await表达式分割开来的。
  • 从第一行代码直到(并包括)第一个await表达式(如果有的话)都是同步运行的。
  • 这样的话,一个不含await表达式的async函数是会同步运行的。
  • 然而,如果函数体内有一个await表达式,async函数就一定会异步执行。
async function foo() {
   const result1 = await new Promise((resolve) => setTimeout(() => resolve('1')))
   const result2 = await new Promise((resolve) => setTimeout(() => resolve('2')))
}
foo()

读取文件示例

const fs = require("fs");
const read = function(fileName){
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName,(err,data)=>{
            if (err) {
                reject(err);
            } else{
                resolve(data);
            }
        });
    });
};
async function readByAsync(){
    let a1;
    let a2;
    let a3;
    try{
        a1 = await read('1.txt');
        a2 = await read('2.txt');
        a3 = await read('3.txt');
    }catch(e){
        //TODO handle the exception
    }
    console.log(a1);
    console.log(a2);
    console.log(a3);
}
readByAsync();

Promise知识汇总和面试情况

Javascript异步编程先后经历了四个阶段,分别是Callback阶段,Promise阶段,Generator阶段和Async/Await阶段。Callback很快就被发现存在回调地狱和控制权问题,Promise就是在这个时间出现,用以解决这些问题,Promise并非一个新事务,而是按照一个规范实现的类,这个规范有很多,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+,最终 ES6 中采用了 Promise/A+ 规范。后来出现的Generator函数以及Async函数也是以Promise为基础的进一步封装,可见Promise在异步编程中的重要性。

关于Promise的资料已经很多,但每个人理解都不一样,不同的思路也会有不一样的收获

实现Promise

规范解读

1、Promise有三种状态pending,fulfilled和rejected。(为了一致性,此文章称fulfilled状态为resolved状态)
状态转换只能是pending到resolved或者pending到rejected;
状态一旦转换完成,不能再次转换。
2、Promise拥有一个then方法,用以处理resolved或rejected状态下的值。
then方法接收两个参数onFulfilled和onRejected,这两个参数变量类型是函数,如果不是函数将会被忽略,并且这两个参数都是可选的。
then方法必须返回一个新的promise,记作promise2,这也就保证了then方法可以在同一个promise上多次调用。(ps:规范只要求返回promise,并没有明确要求返回一个新的promise,这里为了跟ES6实现保持一致,我们也返回一个新promise)
onResolved/onRejected有返回值则把返回值定义为x,并执行[[Resolve]](promise2, x);
onResolved/onRejected运行出错,则把promise2设置为rejected状态;
onResolved/onRejected不是函数,则需要把promise1的状态传递下去。
3、不同的promise实现可以的交互。
规范中称这一步操作为promise解决过程,函数标示为[[Resolve]](promise, x),promise为要返回的新promise对象,x为onResolved/onRejected的返回值。如果x有then方法且看上去像一个promise,我们就把x当成一个promise的对象,即thenable对象,这种情况下尝试让promise接收x的状态。如果x不是thenable对象,就用x的值来执行 promise。
[[Resolve]](promise, x)函数具体运行规则:
如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise;
如果 x 为 Promise ,则使 promise 接受 x 的状态;
如果 x 为对象或者函数,取x.then的值,如果取值时出现错误,则让promise进入rejected状态,如果then不是函数,说明x不是thenable对象,直接以x的值resolve,如果then存在并且为函数,则把x作为then函数的作用域this调用,then方法接收两个参数,resolvePromise和rejectPromise,如果resolvePromise被执行,则以resolvePromise的参数value作为x继续调用[[Resolve]](promise, value),直到x不是对象或者函数,如果rejectPromise被执行则让promise进入rejected状态;
如果 x 不是对象或者函数,直接就用x的值来执行promise。

Promise其他方法实现
1、catch方法
catch方法是对then方法的封装,只用于接收reject(reason)中的错误信息。因为在then方法中onRejected参数是可不传的,不传的情况下,错误信息会依次往后传递,直到有onRejected函数接收为止,因此在写promise链式调用的时候,then方法不传onRejected函数,只需要在最末尾加一个catch()就可以了,这样在该链条中的promise发生的错误都会被最后的catch捕获到。

  catch(onRejected) {
    return this.then(null, onRejected);
  }

2、done方法
catch在promise链式调用的末尾调用,用于捕获链条中的错误信息,但是catch方法内部也可能出现错误,所以有些promise实现中增加了一个方法done,done相当于提供了一个不会出错的catch方法,并且不再返回一个promise,一般用来结束一个promise链。

  done() {
    this.catch(reason => {
      console.log('done', reason);
      throw reason;
    });
  }

3、finally方法
finally方法用于无论是resolve还是reject,finally的参数函数都会被执行。

  finally(fn) {
    return this.then(value => {
      fn();
      return value;
    }, reason => {
      fn();
      throw reason;
    });
  };

4、Promise.all方法
Promise.all方法接收一个promise数组,返回一个新promise2,并发执行数组中的全部promise,所有promise状态都为resolved时,promise2状态为resolved并返回全部promise结果,结果顺序和promise数组顺序一致。如果有一个promise为rejected状态,则整个promise2进入rejected状态。

  static all(promiseList) {
    return new Promise((resolve, reject) => {
      const result = [];
      let i = 0;
      for (const p of promiseList) {
        p.then(value => {
          result[i] = value;
          if (result.length === promiseList.length) {
            resolve(result);
          }
        }, reject);
        i++;
      }
    });
  }

5、Promise.race方法
Promise.race方法接收一个promise数组, 返回一个新promise2,顺序执行数组中的promise,有一个promise状态确定,promise2状态即确定,并且同这个promise的状态一致。

  static race(promiseList) {
    return new Promise((resolve, reject) => {
      for (const p of promiseList) {
        p.then((value) => {
          resolve(value);   
        }, reject);
      }
    });
  }

6、Promise.resolve方法/Promise.reject
Promise.resolve用来生成一个rejected完成态的promise,Promise.reject用来生成一个rejected失败态的promise。

  static resolve(value) {
    let promise;

    promise = new Promise((resolve, reject) => {
      this.resolvePromise(promise, value, resolve, reject);
    });
  
    return promise;
  }
  
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    });
  }

常用的方法基本就这些,Promise还有很多扩展方法,这里就不一一展示,基本上都是对then方法的进一步封装,只要你的then方法没有问题,其他方法就都可以依赖then方法实现。

Promise面试相关

Promise是我司前端开发职位,nodejs开发职位,全栈开发职位,必问的一个知识点,主要问题会分布在Promise介绍、基础使用方法以及深层次的理解三个方面,问题一般在3-5个,根据面试者回答情况会适当增减。

1、简单介绍下Promise。
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。(当然了也可以简单介绍promise状态,有什么方法,callback存在什么问题等等,这个问题是比较开放的)

提问概率:99%
评分标准:人性化判断即可,此问题一般作为引入问题。
加分项:熟练说出Promise具体解决了那些问题,存在什么缺点,应用方向等等。
2、实现一个简单的,支持异步链式调用的Promise类。
这个答案不是固定的,可以参考最简实现 Promise,支持异步链式调用

提问概率:50%(手撸代码题,因为这类题目比较耗费时间,一场面试并不会出现很多,所以出现频率不是很高,但却是必备知识)
加分项:基本功能实现的基础上有onResolved/onRejected函数异步调用,错误捕获合理等亮点。
3、Promise.then在Event Loop中的执行顺序。(可以直接问,也可以出具体题目让面试者回答打印顺序)
JS中分为两种任务类型:macrotask和microtask,其中macrotask包含:主代码块,setTimeout,setInterval,setImmediate等(setImmediate规定:在下一次Event Loop(宏任务)时触发);microtask包含:Promise,process.nextTick等(在node环境下,process.nextTick的优先级高于Promise)Event Loop中执行一个macrotask任务(栈中没有就从事件队列中获取)执行过程中如果遇到microtask任务,就将它添加到微任务的任务队列中,macrotask任务执行完毕后,立即执行当前微任务队列中的所有microtask任务(依次执行),然后开始下一个macrotask任务(从事件队列中获取) 浏览器运行机制可参考这篇文章

提问概率:75%(可以理解为4次面试中3次会问到,顺便可以考察面试者对JS运行机制的理解)
加分项:扩展讲述浏览器运行机制。
4、阐述Promise的一些静态方法。
Promise.deferred、Promise.all、Promise.race、Promise.resolve、Promise.reject等

提问概率:25%(相对基础的问题,一般在其他问题回答不是很理想的情况下提问,或者为了引出下一个题目而提问)
加分项:越多越好
5、Promise存在哪些缺点。
1、无法取消Promise,一旦新建它就会立即执行,无法中途取消。2、如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。3、吞掉错误或异常,错误只能顺序处理,即便在Promise链最后添加catch方法,依然可能存在无法捕捉的错误(catch内部可能会出现错误) 4、阅读代码不是一眼可以看懂,你只会看到一堆then,必须自己在then的回调函数里面理清逻辑。

提问概率:25%(此问题作为提高题目,出现概率不高)
加分项:越多越合理越好(网上有很多说法,不一一佐证) (此题目,欢迎大家补充答案)
6、使用Promise进行顺序(sequence)处理。
1、使用async函数配合await或者使用generator函数配合yield。2、使用promise.then通过for循环或者Array.prototype.reduce实现。

function sequenceTasks(tasks) {
    function recordValue(results, value) {
        results.push(value);
        return results;
    }
    var pushValue = recordValue.bind(null, []);
    return tasks.reduce(function (promise, task) {
        return promise.then(() => task).then(pushValue);
    }, Promise.resolve());
}

提问概率:90%(我司提问概率极高的题目,即能考察面试者对promise的理解程度,又能考察编程逻辑,最后还有bind和reduce等方法的运用)
评分标准:说出任意解决方法即可,其中只能说出async函数和generator函数的可以得到20%的分数,可以用promise.then配合for循环解决的可以得到60%的分数,配合Array.prototype.reduce实现的可以得到最后的20%分数。
7、如何停止一个Promise链?
在要停止的promise链位置添加一个方法,返回一个永远不执行resolve或者reject的Promise,那么这个promise永远处于pending状态,所以永远也不会向下执行then或catch了。这样我们就停止了一个promise链。

Promise.cancel = Promise.stop = function() {
  return new Promise(function(){})
}

提问概率:50%(此问题主要考察面试者罗辑思维) (此题目,欢迎大家补充答案)
8、Promise链上返回的最后一个Promise出错了怎么办?
catch在promise链式调用的末尾调用,用于捕获链条中的错误信息,但是catch方法内部也可能出现错误,所以有些promise实现中增加了一个方法done,done相当于提供了一个不会出错的catch方法,并且不再返回一个promise,一般用来结束一个promise链。

  done() {
    this.catch(reason => {
      console.log('done', reason);
      throw reason;
    });
  }

提问概率:90%(同样作为出题率极高的一个题目,充分考察面试者对promise的理解程度)
加分项:给出具体的done()方法代码实现
9、Promise存在哪些使用技巧或者最佳实践?
1、链式promise要返回一个promise,而不只是构造一个promise。2、合理的使用Promise.all和Promise.race等方法。3、在写promise链式调用的时候,then方法不传onRejected函数,只需要在最末尾加一个catch()就可以了,这样在该链条中的promise发生的错误都会被最后的catch捕获到。如果catch()代码有出现错误的可能,需要在链式调用的末尾增加done()函数。

提问概率:10%(出题概率极低的一个题目)
加分项:越多越好
(此题目,欢迎大家补充答案)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值