Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promise & Async/Await #6

Open
alianzhang opened this issue Mar 30, 2019 · 9 comments
Open

Promise & Async/Await #6

alianzhang opened this issue Mar 30, 2019 · 9 comments
Labels

Comments

@alianzhang
Copy link
Owner

alianzhang commented Mar 30, 2019

Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及该异步操作的结果值。
它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果

一个 Promise有以下几种状态:

pending: 初始状态,既不是成功,也不是失败状态。
fulfilled: 意味着操作成功完成。
rejected: 意味着操作失败。
image

状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多

let p = new Promise(
    /* 执行器 executor */
    function (resolve, reject) {
        // 一段耗时很长的异步操作
        resolve(data); // 数据处理完成
        reject(error); // 数据处理出错
    }
)
p.then(() => {
       // 成功,下一步
  }, () => {
       // 失败,做相应处理
});
// 或用catch来指定reject的回调
p.then((data) => {
     console.log('resolved',data);
}) 
.catch((err) => {
     console.log('rejected',err);
});
.finally(() => {
     // ES9 新增方法
     // 这里可以写无论异步执行结果的状态是成功还是失败都要执行的操作
});       
   

image

参考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises
https://developers.google.com/web/fundamentals/primers/promises

@alianzhang alianzhang added the JS label Nov 18, 2019
@alianzhang
Copy link
Owner Author

alianzhang commented Nov 21, 2019

Promise的all方法:提供了并行执行多个异步操作的能力,并且在所有异步操作执行完后执行回调。

let Promise1 = new Promise(function(resolve, reject){})
let Promise2 = new Promise(function(resolve, reject){})
let Promise3 = new Promise(function(resolve, reject){})

let p = Promise.all([Promise1, Promise2, Promise3])

p.then(funciton(){
  // 三个都成功则成功  
}, function(){
  // 只要有失败,则失败 
})

Promise的race方法:可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作。
谁跑的快,以谁为准执行回调

//请求某个图片资源
   function requestImg(){
       var p = new Promise((resolve, reject) => {
           var img = new Image();
           img.onload = function(){
               resolve(img);
           }
           img.src = '图片的路径';
       });
       return p;
   }
   //延时函数,用于给请求计时
   function timeout(){
       var p = new Promise((resolve, reject) => {
           setTimeout(() => {
               reject('图片请求超时');
           }, 5000);
       });
       return p;
   }
   Promise.race([requestImg(), timeout()]).then((data) =>{
       console.log(data);
   }).catch((err) => {
       console.log(err);
   });

@alianzhang alianzhang added the ES6 label Nov 26, 2019
@alianzhang alianzhang changed the title Promise Promise & Async/Await Dec 18, 2019
@alianzhang
Copy link
Owner Author

alianzhang commented Dec 18, 2019

async/await使得异步代码看起来像同步代码,再也没有回调函数,代码优雅可读性好
async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

  • 使用await,函数必须用async标识
  • await后面跟的是一个Promise实例
  • 需要安装babel-polyfill,安装后记得引入 //npm i --save-dev babel-polyfill

async/await如果想并行执行多个异步操作(通常发生在发送请求时,避免不必要的等待):
1.使用Promise.all

const wait = ms => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(`wait ${ms}ms`)
        resolve(ms)
    }, ms)
})
(async () => {
    await Promise.all([wait(3000), wait(1000), wait(2000)])
    // 依次打印:wait 1000ms wait 2000ms wait 3000ms
})()

2.使用中间变量

const p3 = wait(3000);
const p1 = wait(1000);
const p2 = wait(2000);
const wait3 = await p3;
const wait1 = await p1;
const wait2 = await p2;
// 再执行函数内剩余代码

异常处理:
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

对比举例:
你很可能遇到过这样的场景,调用promise1,使用promise1返回的结果去调用promise2,然后使用两者的结果去调用promise3。你的代码很可能是这样的:

const makeRequest = () => {
  return promise1()
    .then(value1 => {
      return promise2(value1)
        .then(value2 => {        
          return promise3(value1, value2)
        })
    })
}

使用async/await的话,代码会变得异常简单和直观

const makeRequest = async () => {
  const value1 = await promise1()
  const value2 = await promise2(value1)
  return promise3(value1, value2)
}

@alianzhang
Copy link
Owner Author

@alianzhang
Copy link
Owner Author

alianzhang commented May 16, 2020

手写promise1.0版

只实现了成功回调的处理

function MyPromise(fn) {
  let state = "padding";
  let callbacks = []; // promise实例通过.then传来的成功失败回调函数集合
  let res = null;
  function resolve(value) {
    res = value;
    state = "fulfilled";
    setTimeout(function () {
      callbacks.forEach(function (callback) {
        // 把异步函数执行结果->也就是传给resolve的参数,传给回调函数,这样回调函数就拿到了异步操作结果
        callback(value);
      });
    }, 0);
  }
  function reject() {}
  // 立即执行
  fn(resolve);

  // then方法的参数是:成功和失败回调函数
  this.then = function (onFulfilled) {
    // 在异步操作成功之前注册的回调,等待异步完成后执行。异步完成后注册的回调,立即执行
    if (state === "pending") {
      // 存.then传来的成功回调
      callbacks.push(onFulfilled);
    }
    onFulfilled(res);
  };
}

function foo() {
  return new MyPromise(function (resolve) {
    // 模拟异步操作
    // setTimeout(() => {
    //   resolve("1");
    // }, 3000);
    // 同步操作
    resolve("2");
  });
}

foo().then(function (res) {
  console.log(res);
});

@alianzhang
Copy link
Owner Author

alianzhang commented May 16, 2020

手写promise2.0版

成功和失败回调的处理,还没实现链式处理

class MyPromise {
  constructor(executor) {
    // 初始化state为等待状态
    this.state = "pending";
    // 成功的返回值
    this.res = null;
    // 失败的原因
    this.err = null;

    // 存放成功回调函数的数组
    this.onResolvedCallbacks = [];
    // 存放失败回调函数的数组
    this.onRejectedCallbacks = [];

    let resolve = (res) => {
      if (this.state === "pending") {
        state = "fulfilled";
        this.res = res;

        // 一旦resolve执行,调用成功数组中的所有成功回调函数
        this.onResolvedCallbacks.forEach((callback) => {
          callback(res);
        });
      }
    };

    let reject = (err) => {
      if (this.state === "pending") {
        state = "rejected";
        this.err = err;

        // 一旦reject执行,调用失败数组中的所有失败回调函数
        this.onRejectedCallbacks.forEach((callback) => {
          callback(err);
        });
      }
    };
    // 如果executor执行报错,直接执行reject
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfiled, onRejected) {
    // 在executor执行完成之前注册的回调,存起来,等待执行完成后调用。executor执行完成之后注册的回调,立即执行

    // 如果executor是个同步操作,就会执行resolve或reject,state就会改变,然后再执行then回调
    // 状态为fulfilled,执行onFulfilled,传入成功的值
    if (this.state === "fulfilled") {
      onFulfilled(this.res);
    }
    // 状态为rejected,执行onRejected,传入失败的原因
    if (this.state === "rejected") {
      onRejected(this.err);
    }

    // 如果executor是个异步操作,如当resolve或reject在setTomeout内执行,执行then时state还是pending等待状态,我们就需要在then调用的时候,将成功和失败存到各自的数组,等异步执行到reject或者resolve时,再调用它们
    if (this.state === "pending") {
      onFulfiled && this.onResolvedCallbacks.push(onFulfiled);
      onRejected && this.onRejectedCallbacks.push(onRejected);
    }
  }
}

@alianzhang
Copy link
Owner Author

手写promise3.0版

.then的链式调用

class MyPromise {
  constructor(executor) {
    // 初始化state为等待状态
    this.state = "pending";
    // 成功的返回值
    this.res = null;
    // 失败的原因
    this.err = null;
    // 存放成功回调函数的数组
    this.onResolvedCallbacks = [];
    // 存放失败回调函数的数组
    this.onRejectedCallbacks = [];

    let resolve = (res) => {
      if (this.state === "pending") {
        state = "fulfilled";
        this.res = res;

        // 一旦resolve执行,调用成功数组中的所有成功回调函数
        this.onResolvedCallbacks.forEach((callback) => {
          callback();
        });
      }
    };

    let reject = (err) => {
      if (this.state === "pending") {
        state = "rejected";
        this.err = err;

        // 一旦reject执行,调用失败数组中的所有失败回调函数
        this.onRejectedCallbacks.forEach((callback) => {
          callback();
        });
      }
    };
    // 如果executor执行报错,直接执行reject
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  // 1. 参数校验
  // 2. 根据 state, 执行 onFulfiled, onRejected 或者把 执行onFulfiled, onRejected的行为保存在数组
  // 3. A+规定onFulfilled()或onRejected()的值,即then返回的值,叫做x,判断x的函数叫做resolvePromise
  then(onFulfiled, onRejected) {
    // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    // onRejected如果不是函数,就忽略onRejected,直接扔出错误
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (err) => {
            throw err;
          };

    // 在executor执行完成之前注册的回调,存起来,等待执行完成后调用。executor执行完成之后注册的回调,立即执行
    // A+规定onFulfilled或onRejected不能同步被调用,必须异步调用。我们就用setTimeout解决异步问题
    let promise2 = new Promise((resolve, reject) => {
      // 如果executor是个同步操作,就会执行resolve或reject,state就会改变,然后再执行then回调

      // 状态为fulfilled,执行onFulfilled,传入成功的值
      if (this.state === "fulfilled") {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.res);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      // 状态为rejected,执行onRejected,传入失败的原因
      if (this.state === "rejected") {
        setTimeout(() => {
          try {
            let x = onRejected(this.err);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      // 如果executor是个异步操作,如当resolve或reject在setTomeout内执行,执行then时state还是pending等待状态,我们就需要在then调用的时候,将成功和失败存到各自的数组,等异步执行到reject或者resolve时,再调用它们
      if (this.state === "pending") {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.res);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.err);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });
    // 返回promise,完成链式
    return promise2;
  }
}

// 判断x是不是promise,如果是promise,则取它的结果,作为新的promise2的结果
// 如果是普通值,直接作为promise2的结果

function resolvePromise(promise2, x, resolve, reject) {
  // 为了防止循环引用
  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise!"));
  }

  // x不是null 且x是对象或者函数
  if (x != null && (typeof x === "object" || typeof x === "function")) {
    // 拿x.then可能会报错
    try {
      let then = x.then;
      // 防止多次调用
      let called;

      // 如果对象中有then,且then是函数类型,就可以认为是一个Promise对象,之后,使用x作为this来调用then方法
      if (typeof then === "function") {
        // 这里的写法,是 then.call(this, fn1, fn2) 第一个参数是this  后面是成功的回调 和 失败的回调

        // 成功和失败只能调用一个
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            // resolve的结果依旧是promise 那就继续解析
            return resolvePromise(promise2, y, resolve, reject);
          },
          (err) => {
            if (called) return;
            called = true;
            return reject(err); // 失败了就失败了
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      // 取then出错了那就不要在继续执行了
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // x不为null且不是对象或者函数
    resolve(x);
  }
}

@alianzhang
Copy link
Owner Author

@alianzhang
Copy link
Owner Author

链式嵌套 promise 的执行顺序:

执行完当前 promise ,会把紧挨着的 then 放入 microtask 队尾,
链后面的 then 暂不处理(每一个 then 返回一个新的 promise,第二个 then 是第一个 then 返回的 promise 的 then )

@alianzhang
Copy link
Owner Author

image
async/await仅仅影响的是函数内的执行,而不会影响到函数体外的执行顺序。也就是说async1()并不会阻塞后续程序的执行,await async2()相当于一个Promise,console.log("async1 end");相当于前方Promise的then之后执行的函数。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant