evantianx / Bloooooooog

Place to record what I thought and learned
0 stars 0 forks source link

Promise #81

Open evantianx opened 6 years ago

evantianx commented 6 years ago

参考:

JavaScript Promise迷你书

Constructor

ES6 中,使用 Promise() 构造函数可以创建一个 Promise 对象。

const promise = new Promise(function(resolve, reject) {
  // 异步处理
  // 处理结束后调用 resolve 或者 reject
})

实例方法

promise.then(onFulfilled, onRejected)

更推荐 :

promise.then(onFulfilled).catch(onRejected)

Promise#then 不仅仅是注册一个回调函数,它还负责将回调函数返回值包装成一个 promise 对象

静态方法

Promise.all

接受一个 promise 对象的作为参数,只有这个数组里所有 promise 对象全部变成 resolve 或者 reject 的时候才会去调用 .then 方法。

function getURL(URL) {
  return new Promise(function (resolve, reject) {
    var req = new XMLHttpRequest();
    req.open('GET', URL, true);
    req.onload = function () {
      if (req.status === 200) {
        resolve(req.responseText);
      } else {
        reject(new Error(req.statusText));
      }
    };
    req.onerror = function () {
      reject(new Error(req.statusText));
    };
    req.send();
  });
}
var request = {
  comment: function getComment() {
    return getURL('api/json/comment.json').then(JSON.parse);
  },
  people: function getPeople() {
    return getURL('api/json/people.json').then(JSON.parse);
  }
};
function main() {
  return Promise.all([request.comment(), request.people()]);
}
// 运行示例
main().then(function (value) {
  console.log(value);
}).catch(function(error){
  console.log(error);
});

Promise.race()

同样接受一个 promise 对象数组,区别于 promise.all 的是,当其中一个 promise 对象进入 Fulfilled 或者 rejected 状态的话,就会继续进行后面的处理

Promise.resolve()

两种用途:

evantianx commented 6 years ago

Promise 的状态

实例化后的 Promise 对象有三个状态:

0fe49c27f8e1567891b3042bc0c2231e

[[PromiseStatus]] 都是在内部定义的状态。没有公开 API。

状态从 Pending 转换为 Fulfilled 或者 Rejected 后将不再发生任何变化。 这也意味着在 .then 后执行的函数只会执行一次

evantianx commented 6 years ago

创建一个 XHR 的 promise 对象

function getURL(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url, true)
    xhr.onload = () => {
      if (req.status === 200) {
        resolve(req.responseText)
      } else {
        reject(new Error(req.statusText))
      }
    }
    xhr.onerror = () => {
      reject(new Error(req.statusText))
    }
    xhr.send()
  })
}
evantianx commented 6 years ago

Promise 只能异步调用?

是的。

为何?

普遍存在的问题:回调函数中存在同步异步混合调用

const onReady = fn => {
  const readyState = document.readyState
  if (readyState === 'interactive' || readyState === 'complete') {
    fn()
  } else {
    window.addEventListener('DOMContentLoaded', fn)
  }
}

onReady(() => console.log('DOM fully loaded and parsed'))

console.log('----start here----')

上面的代码中出现了在一种情况下回调函数是同步调用,另外一种情况是异步调用。这样会带来很大的混乱,导致开发者完全不知道代码执行顺序会是怎样。

const onReady = fn => {
  const readyState = document.readyState
  if (readyState === 'interactive' || readyState === 'complete') {
    setTimeout(fn, 0)
  } else {
    window.addEventListener('DOMContentLoaded', fn)
  }
}

onReady(() => console.log('DOM fully loaded and parsed'))

console.log('----start here----')

这样修改之后,开发者对于执行流程就会非常明确,回调函数始终是异步调用。

基于异步同步调用混乱问题, Promise 中只存在异步调用:

const onReady = () => {
  return new Promise((resolve, reject) => {
    const readyState = document.readyState
    if (readyState === 'interactive' || readyState === 'complete') {
      resolve()
    } else {
      window.addEventListener('DOMContentLoaded', resolve)
    }
  })
}

onReady()
  .then(() => console.log('DOM fully loaded and parsed'))

console.log('----start here----')
evantianx commented 6 years ago

Promise 的兼容性问题

由于 IE8 不支持 Promise,所以需要利用 polyfill 来模拟实现。

在某些 Promise 实现中,直接使用 promise.catch() 类似的语句会出现 identifier not found 的错误,这是由于catch 属于保留字,而在 ECMAScript3 中保留字是不可以当作属性名使用的。IE8 及以下版本都是基于 ECMAScript3 实现的。

ES5 Identifier Names and Identifiers

如何避免?

使用中括号标记法则可以使用非合法标志符:

// IE8 下使用 Promise polyfill
var promise = Promise.reject(new Error('message'))

promise['catch'](function(error) {
  console.error(error)
})

或者用 promise.then() 代替:

var promise = Promise.reject(new Error('message'))

promise.then(undefined, function(error) {
  console.error(error)
})

目前很多压缩工具自带了将 promise.catch 转换为 promise['catch'] 的功能,帮我们解决了这个问题。

evantianx commented 6 years ago

promise#then or promise#catch

我们在处理错误时,通常会有两种写法:

promise.then(onFulfilled, onRejected)

promise.then(onFulfilled).catch(onRejected)
evantianx commented 6 years ago

应用场景

//  await 实现
const getBook = async bookName => {
  const book = await fetchBook(bookName);

  const author = await fetchAuthor(book.authorId);
  const rating = await fetchRating(book.id);

  return {
    ...book,
    author,
    rating
  };
};

getBook("The Wealth of Nations");
// promise.all + async/await 实现
const getBook = async bookName => {
  const book = await fetchBook(bookName);

  return Promise.all([
    fetchAuthor(book.authorId),
    fetchRating(book.id)
  ]).then(results => ({
    ...book,
    author: results[0],
    rating: results[1]
  }));
};
// promise.all + async/await 实现最终版
const getBook = async bookName => {
  const book = await fetchBook(bookName);

  const [author, rating] = await Promise.all([
    fetchAuthor(book.authorId),
    fetchRating(book.id)
  ]);

  return {
    ...book,
    author,
    rating
  };
};